YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/util/debug-util-test.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
33
#include <signal.h>
34
35
#include <regex>
36
#include <sstream>
37
#include <string>
38
#include <thread>
39
#include <vector>
40
41
#include <glog/logging.h>
42
43
#include "yb/gutil/ref_counted.h"
44
45
#include "yb/util/countdown_latch.h"
46
#include "yb/util/debug-util.h"
47
#include "yb/util/debug/long_operation_tracker.h"
48
#include "yb/util/scope_exit.h"
49
#include "yb/util/test_macros.h"
50
#include "yb/util/test_util.h"
51
#include "yb/util/thread.h"
52
#include "yb/util/tsan_util.h"
53
54
using std::string;
55
using std::vector;
56
57
using namespace std::literals;
58
59
namespace yb {
60
61
class DebugUtilTest : public YBTest {
62
 protected:
63
3
  void WaitForSleeperThreadNameInStackTrace(ThreadIdForStack thread_id) {
64
3
    string stack;
65
3
    for (int i = 0; i < 10000; i++) {
66
3
      stack = DumpThreadStack(thread_id);
67
3
      if (stack.find("SleeperThread") != string::npos) break;
68
0
      SleepFor(MonoDelta::FromMicroseconds(100));
69
0
    }
70
3
    ASSERT_STR_CONTAINS(stack, "SleeperThread");
71
3
  }
72
};
73
74
1
TEST_F(DebugUtilTest, TestStackTrace) {
75
1
  StackTrace t;
76
1
  t.Collect(1);
77
1
  string trace = t.Symbolize();
78
1
  LOG(INFO) << "Trace:\n" << trace;
79
1
  ASSERT_STR_CONTAINS(trace, "yb::DebugUtilTest_TestStackTrace_Test::TestBody");
80
1
  ASSERT_STR_CONTAINS(trace, "testing::internal::UnitTestImpl::RunAllTests()");
81
1
  ASSERT_STR_CONTAINS(trace, "main");
82
1
}
83
84
1
TEST_F(DebugUtilTest, TestGetStackTrace) {
85
1
  string stack_trace = GetStackTrace();
86
87
1
  const std::string kExpectedLineFormatNoFileLineReStr = R"#(^\s*@\s+0x[0-9a-f]+\s+.*)#";
88
1
  SCOPED_TRACE(Format("Regex with no file/line: $0", kExpectedLineFormatNoFileLineReStr));
89
1
  const std::string kFileLineReStr = R"#( \(\S+:\d+\))#";
90
91
1
  const std::regex kExpectedLineFormatNoFileLineRe(kExpectedLineFormatNoFileLineReStr + "$");
92
1
  const std::regex kExpectedLineFormatWithFileLineRe(
93
1
      kExpectedLineFormatNoFileLineReStr + kFileLineReStr + "$");
94
1
  const std::regex kNilUnknownRe(R"#(^\s*@\s+\(nil\)\s+\(unknown\)$)#");
95
96
  // Expected line format:
97
  // @ 0x41255d yb::DebugUtilTest_TestGetStackTrace_Test::TestBody() (yb/util/debug-util-test.cc:73)
98
1
  SCOPED_TRACE(Format("Stack trace to be checked:\n:$0", stack_trace));
99
1
  std::stringstream ss(stack_trace);
100
1
  std::smatch match;
101
102
1
  int with_file_line = 0;
103
1
  int without_file_line = 0;
104
1
  int unmatched = 0;
105
1
  int num_lines = 0;
106
1
  std::ostringstream debug_info;
107
1
  std::string next_line;
108
1
  std::getline(ss, next_line);
109
11
  while (ss) {
110
10
    const auto line = next_line;
111
10
    std::getline(ss, next_line);
112
10
    if (std::regex_match(line, match, kExpectedLineFormatWithFileLineRe)) {
113
0
      ++with_file_line;
114
0
      debug_info << "Line matched regex with file/line number: " << line << std::endl;
115
10
    } else if (std::regex_match(line, match, kExpectedLineFormatNoFileLineRe)) {
116
10
      ++without_file_line;
117
10
      debug_info << "Line matched regex without file/line number: " << line << std::endl;
118
0
    } else if (!ss && std::regex_match(line, match, kNilUnknownRe)) {
119
0
      debug_info << "Last line matched '(nil) (unknown)' pattern: " << line << std::endl;
120
0
    } else {
121
0
      ++unmatched;
122
0
      debug_info << "Line did not match either regex: " << line << std::endl;
123
0
    }
124
10
    ++num_lines;
125
10
  }
126
1
  SCOPED_TRACE(debug_info.str());
127
1
  ASSERT_EQ(unmatched, 0);
128
1
  ASSERT_GE(num_lines, 0);
129
#if defined(__linux__) && !defined(NDEBUG)
130
  ASSERT_GE(with_file_line, 0);
131
#else
132
1
  ASSERT_GE(without_file_line, 0);
133
1
#endif
134
1
}
135
136
// DumpThreadStack is only supported on Linux, since the implementation relies
137
// on the tgkill syscall which is not portable.
138
//
139
// TODO: it might be possible to enable other tests in this section to work on macOS.
140
141
1
TEST_F(DebugUtilTest, TestStackTraceInvalidTid) {
142
#if defined(__linux__)
143
  ThreadIdForStack bad_tid = 1;
144
#else
145
1
  ThreadIdForStack bad_tid = reinterpret_cast<ThreadIdForStack>(1);
146
1
#endif
147
1
  string s = DumpThreadStack(bad_tid);
148
1
  ASSERT_STR_CONTAINS(s, "Unable to deliver signal");
149
1
}
150
151
1
TEST_F(DebugUtilTest, TestStackTraceSelf) {
152
1
  string s = DumpThreadStack(Thread::CurrentThreadIdForStack());
153
1
  ASSERT_STR_CONTAINS(s, "yb::DebugUtilTest_TestStackTraceSelf_Test::TestBody()");
154
1
}
155
156
#if defined(__linux__)
157
158
TEST_F(DebugUtilTest, TestStackTraceMainThread) {
159
  string s = DumpThreadStack(getpid());
160
  ASSERT_STR_CONTAINS(s, "yb::DebugUtilTest_TestStackTraceMainThread_Test::TestBody()");
161
}
162
163
#endif
164
165
namespace {
166
167
1
void SleeperThread(CountDownLatch* l) {
168
  // We use an infinite loop around WaitFor() instead of a normal Wait()
169
  // so that this test passes in TSAN. Without this, we run into this TSAN
170
  // bug which prevents the sleeping thread from handling signals:
171
  // https://code.google.com/p/thread-sanitizer/issues/detail?id=91
172
4
  while (!l->WaitFor(MonoDelta::FromMilliseconds(10))) {
173
3
  }
174
1
}
175
176
0
void fake_signal_handler(int signum) {}
177
178
5
bool IsSignalHandlerRegistered(int signum) {
179
5
  struct sigaction cur_action;
180
5
  CHECK_EQ(0, sigaction(signum, nullptr, &cur_action));
181
5
  return cur_action.sa_handler != SIG_DFL;
182
5
}
183
184
} // anonymous namespace
185
186
1
TEST_F(DebugUtilTest, TestSignalStackTrace) {
187
1
  CountDownLatch l(1);
188
1
  scoped_refptr<Thread> t;
189
1
  ASSERT_OK(Thread::Create("test", "test thread", &SleeperThread, &l, &t));
190
191
  // We have to loop a little bit because it takes a little while for the thread
192
  // to start up and actually call our function.
193
1
  WaitForSleeperThreadNameInStackTrace(t->tid_for_stack());
194
195
  // Test that we can change the signal and that the stack traces still work,
196
  // on the new signal.
197
1
  ASSERT_FALSE(IsSignalHandlerRegistered(SIGUSR1));
198
1
  ASSERT_OK(SetStackTraceSignal(SIGUSR1));
199
200
  // Should now be registered.
201
1
  ASSERT_TRUE(IsSignalHandlerRegistered(SIGUSR1));
202
203
  // SIGUSR2 should be relinquished.
204
1
  ASSERT_FALSE(IsSignalHandlerRegistered(SIGUSR2));
205
206
  // Stack traces should work using the new handler. We've had a test failure here when we ust had
207
  // a one-time check, so we do the same waiting loop as in the beginning of the test.
208
1
  WaitForSleeperThreadNameInStackTrace(t->tid_for_stack());
209
210
  // Switch back to SIGUSR2 and ensure it changes back.
211
1
  ASSERT_OK(SetStackTraceSignal(SIGUSR2));
212
1
  ASSERT_TRUE(IsSignalHandlerRegistered(SIGUSR2));
213
1
  ASSERT_FALSE(IsSignalHandlerRegistered(SIGUSR1));
214
215
  // Stack traces should work using the new handler. Also has a test failure here, so using a retry
216
  // loop.
217
1
  WaitForSleeperThreadNameInStackTrace(t->tid_for_stack());
218
219
  // Register our own signal handler on SIGUSR1, and ensure that
220
  // we get a bad Status if we try to use it.
221
1
  signal(SIGUSR1, &fake_signal_handler);
222
1
  ASSERT_STR_CONTAINS(SetStackTraceSignal(SIGUSR1).ToString(),
223
1
                      "Unable to install signal handler");
224
1
  signal(SIGUSR1, SIG_IGN);
225
226
  // Stack traces should be disabled
227
1
  ASSERT_STR_CONTAINS(DumpThreadStack(t->tid_for_stack()), "Unable to take thread stack");
228
229
  // Re-enable so that other tests pass.
230
1
  ASSERT_OK(SetStackTraceSignal(SIGUSR2));
231
232
  // Allow the thread to finish.
233
1
  l.CountDown();
234
1
  t->Join();
235
1
}
236
237
#if defined(__linux__)
238
239
// Test which dumps all known threads within this process.
240
// We don't validate the results in any way -- but this verifies that we can
241
// dump library threads such as the libc timer_thread and properly time out.
242
TEST_F(DebugUtilTest, TestDumpAllThreads) {
243
  std::vector<pid_t> tids;
244
  ASSERT_OK(ListThreads(&tids));
245
  for (pid_t tid : tids) {
246
    LOG(INFO) << DumpThreadStack(tid);
247
  }
248
}
249
250
#endif
251
252
// This will probably be really slow on Mac OS X, so only enabling on Linux.
253
1
TEST_F(DebugUtilTest, TestGetStackTraceInALoop) {
254
10.0k
  for (int i = 1; i <= 10000; ++i) {
255
10.0k
    GetStackTrace();
256
10.0k
  }
257
1
}
258
259
// TODO: enable this test when we fully fix https://github.com/yugabyte/yugabyte-db/issues/6672.
260
0
TEST_F(DebugUtilTest, YB_DISABLE_TEST(GetStackTraceParallelWithDumpThreadStack)) {
261
0
  std::atomic<ThreadIdForStack> get_stack_trace_thread_id;
262
263
0
  CountDownLatch get_stack_trace_thread_started(1);
264
0
  CountDownLatch dump_started(1);
265
266
0
  std::thread thread1([&] {
267
0
    get_stack_trace_thread_id = Thread::CurrentThreadIdForStack();
268
0
    get_stack_trace_thread_started.CountDown();
269
0
    dump_started.Wait();
270
0
    LOG(INFO) << "Starting GetStacktrace loop";
271
0
    for (int i = 0; i < 10000; i++) {
272
0
      const auto s = GetStackTrace();
273
0
      if (i == 0) {
274
0
        LOG(INFO) << "My stack trace: " << s;
275
0
      }
276
0
    }
277
0
  });
278
279
0
  get_stack_trace_thread_started.Wait();
280
0
  std::vector<std::thread> dump_threads;
281
0
  for (int t = 0; t < 10; ++t) {
282
0
    dump_threads.emplace_back([&] {
283
0
      const auto thread_id = get_stack_trace_thread_id.load();
284
0
      LOG(INFO) << "Starting dump loop";
285
0
      for (int i = 0; i < 10000; ++i) {
286
0
        const auto s = ThreadStack(thread_id);
287
0
        if (i == 0) {
288
0
          LOG(INFO) << (s.ok() ? "Got stacktrace" : AsString(s.status()));
289
0
        }
290
0
      }
291
0
    });
292
0
  }
293
0
  LOG(INFO) << "Started dump loops";
294
0
  std::this_thread::sleep_for(1s);
295
0
  dump_started.CountDown();
296
297
0
  thread1.join();
298
0
  for (auto& thread : dump_threads) {
299
0
    thread.join();
300
0
  }
301
0
  LOG(INFO) << "Done";
302
0
}
303
304
1
TEST_F(DebugUtilTest, TestConcurrentStackTrace) {
305
1
  constexpr size_t kTotalThreads = 10;
306
1
  constexpr size_t kNumCycles = 100;
307
1
  std::vector<std::thread> threads;
308
1
  LOG(INFO) << "Spawning threads";
309
11
  while (threads.size() != kTotalThreads) {
310
10
    threads.emplace_back([] {
311
10
      LOG(INFO) << "Thread started";
312
1.00k
      for (size_t i = 0; i != kNumCycles; ++i) {
313
998
        GetStackTrace();
314
998
      }
315
10
      LOG(INFO) << "Thread finished";
316
10
    });
317
10
  }
318
319
1
  LOG(INFO) << "Joining thread";
320
10
  for (auto& thread : threads) {
321
10
    thread.join();
322
10
  }
323
1
}
324
325
1
TEST_F(DebugUtilTest, LongOperationTracker) {
326
1
  class TestLogSink : public google::LogSink {
327
1
   public:
328
1
    void send(google::LogSeverity severity, const char* full_filename,
329
1
              const char* base_filename, int line,
330
1
              const struct ::tm* tm_time,
331
4
              const char* message, size_t message_len) override {
332
4
      std::lock_guard<std::mutex> lock(mutex_);
333
4
      log_messages_.emplace_back(message, message_len);
334
4
    }
335
336
1
    size_t MessagesSize() {
337
1
      std::lock_guard<std::mutex> lock(mutex_);
338
1
      return log_messages_.size();
339
1
    }
340
341
4
    std::string MessageAt(size_t idx) {
342
4
      std::lock_guard<std::mutex> lock(mutex_);
343
4
      return log_messages_[idx];
344
4
    }
345
346
1
   private:
347
1
    std::mutex mutex_;
348
1
    std::vector<std::string> log_messages_;
349
1
  };
350
351
1
#ifndef NDEBUG
352
1
  const auto kTimeMultiplier = RegularBuildVsSanitizers(3, 10);
353
#else
354
  const auto kTimeMultiplier = 1;
355
#endif
356
357
1
  const auto kShortDuration = 100ms * kTimeMultiplier;
358
1
  const auto kMidDuration = 400ms * kTimeMultiplier;
359
1
  const auto kLongDuration = 1000ms * kTimeMultiplier;
360
1
  TestLogSink log_sink;
361
1
  google::AddLogSink(&log_sink);
362
1
  auto se = ScopeExit([&log_sink] {
363
1
    google::RemoveLogSink(&log_sink);
364
1
  });
365
366
1
  {
367
1
    LongOperationTracker tracker("Op1", kLongDuration);
368
1
    std::this_thread::sleep_for(kShortDuration);
369
1
  }
370
1
  {
371
1
    LongOperationTracker tracker("Op2", kShortDuration);
372
1
    std::this_thread::sleep_for(kLongDuration);
373
1
  }
374
1
  {
375
1
    LongOperationTracker tracker1("Op3", kLongDuration);
376
1
    LongOperationTracker tracker2("Op4", kShortDuration);
377
1
    std::this_thread::sleep_for(kMidDuration);
378
1
  }
379
380
1
  std::this_thread::sleep_for(kLongDuration);
381
382
1
  ASSERT_EQ(log_sink.MessagesSize(), 4);
383
1
  ASSERT_STR_CONTAINS(log_sink.MessageAt(0), "Op2");
384
1
  ASSERT_STR_CONTAINS(log_sink.MessageAt(1), "Op2");
385
1
  ASSERT_STR_CONTAINS(log_sink.MessageAt(2), "Op4");
386
1
  ASSERT_STR_CONTAINS(log_sink.MessageAt(3), "Op4");
387
1
}
388
389
} // namespace yb