blob: 0ed0b7aab690d30a82135ac5180a4ec641d2ff47 [file] [log] [blame]
/*
* 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