| /* | |
| * Copyright (C) 2021 The Android Open Source Project | |
| * | |
| * Licensed 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. | |
| */ | |
| #include "perfetto/base/logging.h" | |
| #include <stdint.h> | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <thread> | |
| #include <vector> | |
| #include "perfetto/ext/base/crash_keys.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "src/base/log_ring_buffer.h" | |
| #include "test/gtest_and_gmock.h" | |
| namespace perfetto { | |
| namespace base { | |
| namespace { | |
| char g_last_line[256]; | |
| TEST(LoggingTest, Basic) { | |
| SetLogMessageCallback(nullptr); | |
| LogMessage(kLogDebug, "file.cc", 100, "test message %d", 1); | |
| SetLogMessageCallback(+[](LogMessageCallbackArgs log) { | |
| base::SprintfTrunc(g_last_line, sizeof(g_last_line), "%d:%s:%d:%s", | |
| log.level, log.filename, log.line, log.message); | |
| }); | |
| g_last_line[0] = 0; | |
| LogMessage(kLogDebug, "file.cc", 101, "test message %d", 2); | |
| ASSERT_STREQ(g_last_line, "0:file.cc:101:test message 2"); | |
| g_last_line[0] = 0; | |
| SetLogMessageCallback(nullptr); | |
| LogMessage(kLogDebug, "file.cc", 102, "test message %d", 3); | |
| ASSERT_STREQ(g_last_line, ""); | |
| } | |
| TEST(LogRingBufferTest, SimpleCases) { | |
| char buf[4096]; | |
| memset(buf, 'x', sizeof(buf)); // Deliberately not 0-initialized. | |
| LogRingBuffer lrb; | |
| EXPECT_EQ(0u, lrb.Read(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, ""); | |
| // Append one entry and read back. | |
| lrb.Append("tstamp1,", "src1.cc", "message1"); | |
| EXPECT_EQ(25u, lrb.Read(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, "tstamp1,src1.cc message1\n"); | |
| lrb.Append("tstamp2,", "src2.cc", "message2"); | |
| EXPECT_EQ(50u, lrb.Read(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, "tstamp1,src1.cc message1\ntstamp2,src2.cc message2\n"); | |
| } | |
| TEST(LogRingBufferTest, Truncation) { | |
| // Append a long entry that overflows the event slot. | |
| std::string long_msg; | |
| long_msg.resize(kLogRingBufMsgLen * 2); | |
| for (size_t i = 0; i < long_msg.size(); i++) | |
| long_msg[i] = static_cast<char>('a' + (i % 27)); | |
| LogRingBuffer lrb; | |
| lrb.Append("A", "B", StringView(long_msg)); | |
| // Check that it gets truncated with no side effects. | |
| char buf[4096]; | |
| memset(buf, 'x', sizeof(buf)); // Deliberately not 0-initialized. | |
| auto expected = "AB " + long_msg.substr(0, kLogRingBufMsgLen - 4) + "\n"; | |
| EXPECT_EQ(expected.size(), lrb.Read(buf, sizeof(buf))); | |
| EXPECT_EQ(buf, expected); | |
| // Append a short message and check everything still works. | |
| lrb.Append("X", "Y", "foo"); | |
| EXPECT_EQ(expected.size() + 7, lrb.Read(buf, sizeof(buf))); | |
| EXPECT_EQ(buf, expected + "XY foo\n"); | |
| } | |
| TEST(LogRingBufferTest, Wrapping) { | |
| LogRingBuffer lrb; | |
| std::vector<std::string> expected_logs; | |
| for (uint32_t i = 0; i < 128; i++) { | |
| std::string id = std::to_string(i); | |
| std::string tstamp = "tstamp" + id + ","; | |
| std::string src = "src"; | |
| std::string msg; | |
| msg.resize(1 + (i % 16)); | |
| for (size_t c = 0; c < msg.size(); c++) | |
| msg[c] = static_cast<char>('a' + c); | |
| lrb.Append(StringView(tstamp), StringView(src), StringView(msg)); | |
| auto expected_log = | |
| (tstamp + src + " " + msg).substr(0, kLogRingBufMsgLen) + "\n"; | |
| expected_logs.emplace_back(expected_log); | |
| } | |
| std::string expected; | |
| for (size_t i = expected_logs.size() - kLogRingBufEntries; | |
| i < expected_logs.size(); i++) { | |
| expected += expected_logs[i]; | |
| } | |
| char buf[kLogRingBufMsgLen * kLogRingBufEntries]; | |
| memset(buf, 'x', sizeof(buf)); // Deliberately not 0-initialized. | |
| lrb.Read(buf, sizeof(buf)); | |
| EXPECT_EQ(buf, expected); | |
| // Do a partial readback which will cause output truncation. | |
| lrb.Read(buf, 127); | |
| EXPECT_EQ(buf, expected.substr(0, 127 - 1)); // - 1 for the NUL terminator. | |
| } | |
| // Writes concurrently into the ring buffer and check that all the events are | |
| // seen in some order. | |
| TEST(LogRingBufferTest, MultiThreadedWrites) { | |
| LogRingBuffer lrb; | |
| std::vector<std::thread> threads; | |
| const size_t kNumThreads = 8; | |
| std::mutex mutex; | |
| std::condition_variable cond; | |
| bool sync_start = false; | |
| auto thread_main = [&](size_t thread_idx) { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| cond.wait(lock, [&] { return sync_start; }); | |
| std::string tstamp = "ts" + std::to_string(thread_idx) + ","; | |
| std::string src = "src"; | |
| std::string msg(thread_idx + 1, '.'); // A variable number of dots. | |
| lrb.Append(StringView(tstamp), StringView(src), StringView(msg)); | |
| }; | |
| std::vector<std::string> expected_events; | |
| for (size_t i = 0; i < kNumThreads; i++) { | |
| threads.emplace_back(thread_main, i); | |
| std::string id = std::to_string(i); | |
| expected_events.emplace_back("ts" + id + ",src " + std::string(i + 1, '.')); | |
| } | |
| // Unlock all the threads as close as possible to maximize races. | |
| { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| sync_start = true; | |
| cond.notify_all(); | |
| } | |
| for (auto& thread : threads) | |
| thread.join(); | |
| char buf[kLogRingBufEntries * 40]; | |
| memset(buf, 'x', sizeof(buf)); // Deliberately not 0-initialized. | |
| lrb.Read(buf, sizeof(buf)); | |
| std::vector<std::string> actual_events = SplitString(buf, "\n"); | |
| EXPECT_THAT(actual_events, | |
| testing::UnorderedElementsAreArray(expected_events)); | |
| } | |
| TEST(CrashKeysTest, SetClearAndLongKeys) { | |
| UnregisterAllCrashKeysForTesting(); | |
| char buf[1024]; | |
| memset(buf, 'x', sizeof(buf)); | |
| EXPECT_EQ(0u, SerializeCrashKeys(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, ""); | |
| CrashKey k1("key1"); | |
| CrashKey k2("key2"); | |
| CrashKey k3("key3"); | |
| CrashKey k4("key4"); | |
| k1.Set(0); | |
| k1.Clear(); | |
| k2.Set(42); | |
| k3.Set("xx"); | |
| k3.Clear(); | |
| k4.Set("value"); | |
| EXPECT_EQ(21u, SerializeCrashKeys(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, "key2: 42\nkey4: value\n"); | |
| EXPECT_EQ(0u, SerializeCrashKeys(buf, 0)); | |
| EXPECT_EQ(0u, SerializeCrashKeys(buf, 1)); | |
| EXPECT_STREQ(buf, ""); | |
| // Test truncated output. | |
| EXPECT_EQ(5u, SerializeCrashKeys(buf, 5 + 1)); | |
| EXPECT_STREQ(buf, "key2:"); | |
| k2.Clear(); | |
| std::string long_str(1024, 'x'); | |
| k4.Set(StringView(long_str)); | |
| EXPECT_EQ(6 + kCrashKeyMaxStrSize, SerializeCrashKeys(buf, sizeof(buf))); | |
| std::string expected = | |
| "key4: " + long_str.substr(0, kCrashKeyMaxStrSize - 1) + "\n"; | |
| EXPECT_EQ(buf, expected); | |
| UnregisterAllCrashKeysForTesting(); | |
| } | |
| TEST(CrashKeysTest, ScopedSet) { | |
| UnregisterAllCrashKeysForTesting(); | |
| char buf[1024]; | |
| memset(buf, 'x', sizeof(buf)); | |
| CrashKey k1("key1"); | |
| CrashKey k2("key2"); | |
| auto scoped_key = k1.SetScoped(42); | |
| EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u); | |
| EXPECT_STREQ(buf, "key1: 42\n"); | |
| { | |
| auto scoped_key2 = k2.SetScoped("foo"); | |
| EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u); | |
| EXPECT_STREQ(buf, "key1: 42\nkey2: foo\n"); | |
| } | |
| EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u); | |
| EXPECT_STREQ(buf, "key1: 42\n"); | |
| k1.Clear(); | |
| EXPECT_EQ(0u, SerializeCrashKeys(buf, sizeof(buf))); | |
| EXPECT_STREQ(buf, ""); | |
| UnregisterAllCrashKeysForTesting(); | |
| } | |
| } // namespace | |
| } // namespace base | |
| } // namespace perfetto |