/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 |