Merge "logd: replay the entire log when UID clearing"
diff --git a/logd/Android.bp b/logd/Android.bp
index 6306c73..6f23808 100644
--- a/logd/Android.bp
+++ b/logd/Android.bp
@@ -153,6 +153,7 @@
"ChattyLogBufferTest.cpp",
"logd_test.cpp",
"LogBufferTest.cpp",
+ "SerializedLogBufferTest.cpp",
"SerializedLogChunkTest.cpp",
"SerializedFlushToStateTest.cpp",
],
diff --git a/logd/SerializedLogBuffer.cpp b/logd/SerializedLogBuffer.cpp
index 7446813..0101c17 100644
--- a/logd/SerializedLogBuffer.cpp
+++ b/logd/SerializedLogBuffer.cpp
@@ -27,6 +27,70 @@
#include "LogStatistics.h"
#include "SerializedFlushToState.h"
+static SerializedLogEntry* LogToLogBuffer(std::list<SerializedLogChunk>& log_buffer,
+ size_t max_size, uint64_t sequence, log_time realtime,
+ uid_t uid, pid_t pid, pid_t tid, const char* msg,
+ uint16_t len) {
+ if (log_buffer.empty()) {
+ log_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));
+ }
+
+ auto total_len = sizeof(SerializedLogEntry) + len;
+ if (!log_buffer.back().CanLog(total_len)) {
+ log_buffer.back().FinishWriting();
+ log_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));
+ }
+
+ return log_buffer.back().Log(sequence, realtime, uid, pid, tid, msg, len);
+}
+
+// Clear all logs from a particular UID by iterating through all existing logs for a log_id, and
+// write all logs that don't match the UID into a new log buffer, then swapping the log buffers.
+// There is an optimization that chunks are copied as-is until a log message from the UID is found,
+// to ensure that back-to-back clears of the same UID do not require reflowing the entire buffer.
+void ClearLogsByUid(std::list<SerializedLogChunk>& log_buffer, uid_t uid, size_t max_size,
+ log_id_t log_id, LogStatistics* stats) REQUIRES(logd_lock) {
+ bool contains_uid_logs = false;
+ std::list<SerializedLogChunk> new_logs;
+ auto it = log_buffer.begin();
+ while (it != log_buffer.end()) {
+ auto chunk = it++;
+ chunk->NotifyReadersOfPrune(log_id);
+ chunk->IncReaderRefCount();
+
+ if (!contains_uid_logs) {
+ for (const auto& entry : *chunk) {
+ if (entry.uid() == uid) {
+ contains_uid_logs = true;
+ break;
+ }
+ }
+ if (!contains_uid_logs) {
+ chunk->DecReaderRefCount();
+ new_logs.splice(new_logs.end(), log_buffer, chunk);
+ continue;
+ }
+ // We found a UID log, so push a writable chunk to prepare for the next loop.
+ new_logs.push_back(
+ SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));
+ }
+
+ for (const auto& entry : *chunk) {
+ if (entry.uid() == uid) {
+ if (stats != nullptr) {
+ stats->Subtract(entry.ToLogStatisticsElement(log_id));
+ }
+ } else {
+ LogToLogBuffer(new_logs, max_size, entry.sequence(), entry.realtime(), entry.uid(),
+ entry.pid(), entry.tid(), entry.msg(), entry.msg_len());
+ }
+ }
+ chunk->DecReaderRefCount();
+ log_buffer.erase(chunk);
+ }
+ std::swap(new_logs, log_buffer);
+}
+
SerializedLogBuffer::SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags,
LogStatistics* stats)
: reader_list_(reader_list), tags_(tags), stats_(stats) {
@@ -87,18 +151,8 @@
auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);
auto lock = std::lock_guard{logd_lock};
-
- if (logs_[log_id].empty()) {
- logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
- }
-
- auto total_len = sizeof(SerializedLogEntry) + len;
- if (!logs_[log_id].back().CanLog(total_len)) {
- logs_[log_id].back().FinishWriting();
- logs_[log_id].push_back(SerializedLogChunk(max_size_[log_id] / 4));
- }
-
- auto entry = logs_[log_id].back().Log(sequence, realtime, uid, pid, tid, msg, len);
+ auto entry = LogToLogBuffer(logs_[log_id], max_size_[log_id], sequence, realtime, uid, pid, tid,
+ msg, len);
stats_->Add(entry->ToLogStatisticsElement(log_id));
MaybePrune(log_id);
@@ -111,7 +165,7 @@
size_t total_size = GetSizeUsed(log_id);
size_t after_size = total_size;
if (total_size > max_size_[log_id]) {
- Prune(log_id, total_size - max_size_[log_id], 0);
+ Prune(log_id, total_size - max_size_[log_id]);
after_size = GetSizeUsed(log_id);
LOG(VERBOSE) << "Pruned Logs from log_id: " << log_id << ", previous size: " << total_size
<< " after size: " << after_size;
@@ -122,16 +176,13 @@
void SerializedLogBuffer::RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk) {
chunk.IncReaderRefCount();
- int read_offset = 0;
- while (read_offset < chunk.write_offset()) {
- auto* entry = chunk.log_entry(read_offset);
- stats_->Subtract(entry->ToLogStatisticsElement(log_id));
- read_offset += entry->total_len();
+ for (const auto& entry : chunk) {
+ stats_->Subtract(entry.ToLogStatisticsElement(log_id));
}
chunk.DecReaderRefCount();
}
-void SerializedLogBuffer::Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) {
+void SerializedLogBuffer::Prune(log_id_t log_id, size_t bytes_to_free) {
auto& log_buffer = logs_[log_id];
auto it = log_buffer.begin();
while (it != log_buffer.end()) {
@@ -168,22 +219,28 @@
// Notify them to delete the reference.
it_to_prune->NotifyReadersOfPrune(log_id);
- if (uid != 0) {
- // Reorder the log buffer to remove logs from the given UID. If there are no logs left
- // in the buffer after the removal, delete it.
- if (it_to_prune->ClearUidLogs(uid, log_id, stats_)) {
- log_buffer.erase(it_to_prune);
- }
- } else {
- size_t buffer_size = it_to_prune->PruneSize();
- RemoveChunkFromStats(log_id, *it_to_prune);
- log_buffer.erase(it_to_prune);
- if (buffer_size >= bytes_to_free) {
- return;
- }
- bytes_to_free -= buffer_size;
+ size_t buffer_size = it_to_prune->PruneSize();
+ RemoveChunkFromStats(log_id, *it_to_prune);
+ log_buffer.erase(it_to_prune);
+ if (buffer_size >= bytes_to_free) {
+ return;
+ }
+ bytes_to_free -= buffer_size;
+ }
+}
+
+void SerializedLogBuffer::UidClear(log_id_t log_id, uid_t uid) {
+ // Wake all readers; see comment in Prune().
+ for (const auto& reader_thread : reader_list_->reader_threads()) {
+ if (!reader_thread->IsWatching(log_id)) {
+ continue;
+ }
+
+ if (reader_thread->deadline().time_since_epoch().count() != 0) {
+ reader_thread->TriggerReader();
}
}
+ ClearLogsByUid(logs_[log_id], uid, max_size_[log_id], log_id, stats_);
}
std::unique_ptr<FlushToState> SerializedLogBuffer::CreateFlushToState(uint64_t start,
@@ -243,7 +300,11 @@
bool SerializedLogBuffer::Clear(log_id_t id, uid_t uid) {
auto lock = std::lock_guard{logd_lock};
- Prune(id, ULONG_MAX, uid);
+ if (uid == 0) {
+ Prune(id, ULONG_MAX);
+ } else {
+ UidClear(id, uid);
+ }
// Clearing SerializedLogBuffer never waits for readers and therefore is always successful.
return true;
diff --git a/logd/SerializedLogBuffer.h b/logd/SerializedLogBuffer.h
index 4ce7a3b..ca78c42 100644
--- a/logd/SerializedLogBuffer.h
+++ b/logd/SerializedLogBuffer.h
@@ -36,6 +36,9 @@
class SerializedLogBuffer final : public LogBuffer {
public:
+ // Create SerializedLogChunk's with size = max_size_[log_id] / kChunkSizeDivisor.
+ static constexpr size_t kChunkSizeDivisor = 4;
+
SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags, LogStatistics* stats);
void Init() override;
@@ -57,7 +60,8 @@
private:
bool ShouldLog(log_id_t log_id, const char* msg, uint16_t len);
void MaybePrune(log_id_t log_id) REQUIRES(logd_lock);
- void Prune(log_id_t log_id, size_t bytes_to_free, uid_t uid) REQUIRES(logd_lock);
+ void Prune(log_id_t log_id, size_t bytes_to_free) REQUIRES(logd_lock);
+ void UidClear(log_id_t log_id, uid_t uid) REQUIRES(logd_lock);
void RemoveChunkFromStats(log_id_t log_id, SerializedLogChunk& chunk);
size_t GetSizeUsed(log_id_t id) REQUIRES(logd_lock);
@@ -70,3 +74,7 @@
std::atomic<uint64_t> sequence_ = 1;
};
+
+// Exposed for testing.
+void ClearLogsByUid(std::list<SerializedLogChunk>& log_buffer, uid_t uid, size_t max_size,
+ log_id_t log_id, LogStatistics* stats);
\ No newline at end of file
diff --git a/logd/SerializedLogBufferTest.cpp b/logd/SerializedLogBufferTest.cpp
new file mode 100644
index 0000000..dbbe2f2
--- /dev/null
+++ b/logd/SerializedLogBufferTest.cpp
@@ -0,0 +1,398 @@
+/*
+ * 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 "SerializedLogBuffer.h"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include "LogReaderList.h"
+#include "LogSize.h"
+#include "LogStatistics.h"
+#include "LogTags.h"
+#include "SerializedLogChunk.h"
+#include "SerializedLogEntry.h"
+
+// b/188858988 - Previously, if clearing UID logs erased the back-most chunk, then a chunk that has
+// previously been closed for writing will be the back-most chunk in the list. Subsequent calls to
+// SerializedLogBuffer::Log() will call SerializedLogChunk::FinishWriting(), which triggered a
+// CHECK().
+TEST(SerializedLogBuffer, uid_prune_deletes_last_chunk) {
+ LogReaderList reader_list;
+ LogTags tags;
+ LogStatistics stats(false, true);
+ SerializedLogBuffer log_buffer(&reader_list, &tags, &stats);
+
+ log_buffer.SetSize(LOG_ID_MAIN, kLogBufferMinSize);
+ log_buffer.Clear(LOG_ID_MAIN, 0);
+
+ const uid_t kNonClearUid = 123;
+ const std::string kDontClearMessage = "this message is not cleared";
+ log_buffer.Log(LOG_ID_MAIN, log_time(0, 1), kNonClearUid, 1, 1, kDontClearMessage.data(),
+ kDontClearMessage.size());
+
+ // Fill at least one chunk with a message with the UID that we'll clear
+ const uid_t kClearUid = 555;
+ const std::string kClearMessage(1024, 'c');
+ size_t size_written = 0;
+ while (size_written < kLogBufferMinSize / 2) {
+ log_buffer.Log(LOG_ID_MAIN, log_time(1, size_written), kClearUid, 1, 1,
+ kClearMessage.data(), kClearMessage.size());
+ size_written += kClearMessage.size();
+ }
+
+ log_buffer.Clear(LOG_ID_MAIN, kClearUid);
+
+ // This previously would trigger a CHECK() in SerializedLogChunk::FinishWriting().
+ log_buffer.Log(LOG_ID_MAIN, log_time(0, 2), kNonClearUid, 1, 1, kDontClearMessage.data(),
+ kDontClearMessage.size());
+}
+
+struct TestEntry {
+ uint32_t uid;
+ uint32_t pid;
+ uint32_t tid;
+ uint64_t sequence;
+ log_time realtime;
+ std::string msg;
+};
+
+SerializedLogChunk CreateChunk(size_t max_size, const std::vector<TestEntry>& entries,
+ bool finish_writing) {
+ SerializedLogChunk chunk(max_size / SerializedLogBuffer::kChunkSizeDivisor);
+
+ for (const auto& entry : entries) {
+ if (!chunk.CanLog(sizeof(SerializedLogEntry) + entry.msg.size())) {
+ EXPECT_TRUE(false) << "Test set up failure, entries don't fit in chunks";
+ return chunk;
+ }
+ chunk.Log(entry.sequence, entry.realtime, entry.uid, entry.pid, entry.tid, entry.msg.data(),
+ entry.msg.size());
+ }
+ if (finish_writing) {
+ chunk.FinishWriting();
+ }
+ return chunk;
+}
+
+void VerifyChunks(const std::list<SerializedLogChunk>& expected,
+ const std::list<SerializedLogChunk>& chunks) {
+ int chunk = 0;
+ auto expected_it = expected.begin();
+ auto it = chunks.begin();
+ for (; expected_it != expected.end() && it != chunks.end(); ++expected_it, ++it, ++chunk) {
+ EXPECT_EQ(expected_it->reader_ref_count_, it->reader_ref_count_) << "chunk #" << chunk;
+ EXPECT_EQ(expected_it->writer_active_, it->writer_active_) << "chunk #" << chunk;
+ EXPECT_EQ(expected_it->highest_sequence_number_, it->highest_sequence_number_)
+ << "chunk #" << chunk;
+ EXPECT_EQ(expected_it->readers_, it->readers_) << "chunk #" << chunk;
+
+ ASSERT_EQ(expected_it->contents_.size(), it->contents_.size()) << "chunk #" << chunk;
+ ASSERT_EQ(expected_it->write_offset_, it->write_offset_) << "chunk #" << chunk;
+ if (expected_it->contents_.size() > 0) {
+ for (int i = 0; i < it->write_offset_; ++i) {
+ EXPECT_EQ(expected_it->contents_.data()[i], it->contents_.data()[i])
+ << "chunk #" << chunk;
+ }
+ }
+
+ ASSERT_EQ(expected_it->compressed_log_.size(), it->compressed_log_.size())
+ << "chunk #" << chunk;
+ if (expected_it->compressed_log_.size() > 0) {
+ for (size_t i = 0; i < it->compressed_log_.size(); ++i) {
+ EXPECT_EQ(expected_it->compressed_log_.data()[i], it->compressed_log_.data()[i])
+ << "chunk #" << chunk;
+ }
+ }
+ }
+ EXPECT_EQ(expected.end(), expected_it);
+ EXPECT_EQ(chunks.end(), it);
+}
+
+// If no blocks are present before ClearLogsByUid() then no blocks should be output.
+TEST(SerializedLogBuffer, uid_prune_no_blocks) {
+ const uid_t kClearUid = 555;
+ const size_t kMaxSize = kLogBufferMinSize;
+
+ std::list<SerializedLogChunk> chunks;
+ std::list<SerializedLogChunk> expected_chunks;
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+}
+
+// If no UIDs to be cleared are found, then the _same exact_ block is returned.
+TEST(SerializedLogBuffer, uid_prune_one_block_no_uid) {
+ const uid_t kNonClearUid = 123;
+ const uid_t kClearUid = 555;
+ const size_t kMaxSize = kLogBufferMinSize;
+
+ std::vector<TestEntry> entries = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 1,
+ .realtime = log_time(0, 1),
+ .msg = "some message"},
+ };
+
+ std::list<SerializedLogChunk> chunks;
+ chunks.emplace_back(CreateChunk(kMaxSize, entries, false));
+ void* original_chunk_address = reinterpret_cast<void*>(&chunks.front());
+
+ std::list<SerializedLogChunk> expected_chunks;
+ expected_chunks.push_back(CreateChunk(kMaxSize, entries, false));
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+ void* after_clear_chunk_address = reinterpret_cast<void*>(&chunks.front());
+ EXPECT_EQ(original_chunk_address, after_clear_chunk_address);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+ after_clear_chunk_address = reinterpret_cast<void*>(&chunks.front());
+ EXPECT_EQ(original_chunk_address, after_clear_chunk_address);
+}
+
+std::vector<TestEntry> FilterEntries(const std::vector<TestEntry>& entries, uid_t uid_to_remove) {
+ std::vector<TestEntry> filtered_entries;
+ for (const auto& entry : entries) {
+ if (entry.uid == uid_to_remove) {
+ continue;
+ }
+ filtered_entries.emplace_back(entry);
+ }
+ return filtered_entries;
+}
+
+TEST(SerializedLogBuffer, uid_prune_one_block_some_uid) {
+ const uid_t kNonClearUid = 123;
+ const uid_t kClearUid = 555;
+ const size_t kMaxSize = kLogBufferMinSize;
+
+ std::list<SerializedLogChunk> chunks;
+ std::vector<TestEntry> entries = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 1,
+ .realtime = log_time(0, 1),
+ .msg = "some message"},
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 2,
+ .realtime = log_time(0, 1),
+ .msg = "some cleared message"},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries, false));
+
+ std::list<SerializedLogChunk> expected_chunks;
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, FilterEntries(entries, kClearUid), false));
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+}
+
+TEST(SerializedLogBuffer, uid_prune_one_block_all_uid) {
+ const uid_t kClearUid = 555;
+ const size_t kMaxSize = kLogBufferMinSize;
+
+ std::list<SerializedLogChunk> chunks;
+ std::vector<TestEntry> entries = {
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 1,
+ .realtime = log_time(0, 1),
+ .msg = "some message"},
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 2,
+ .realtime = log_time(0, 1),
+ .msg = "some cleared message"},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries, false));
+
+ std::list<SerializedLogChunk> expected_chunks;
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, {}, false));
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+}
+
+TEST(SerializedLogBuffer, uid_prune_first_middle_last_chunks) {
+ const uid_t kNonClearUid = 123;
+ const uid_t kClearUid = 555;
+ const std::string kMsg = "constant log message";
+ const size_t kMaxSize =
+ (sizeof(SerializedLogEntry) + kMsg.size()) * SerializedLogBuffer::kChunkSizeDivisor;
+
+ std::list<SerializedLogChunk> chunks;
+ std::vector<TestEntry> entries0 = {
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 1,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries0, true));
+ std::vector<TestEntry> entries1 = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 2,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries1, true));
+ std::vector<TestEntry> entries2 = {
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 3,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries2, true));
+ std::vector<TestEntry> entries3 = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 4,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries3, true));
+ std::vector<TestEntry> entries4 = {
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 5,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries4, false));
+
+ std::list<SerializedLogChunk> expected_chunks;
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, entries1, true));
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, entries3, false));
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+}
+
+TEST(SerializedLogBuffer, uid_prune_coalesce) {
+ const uid_t kNonClearUid = 123;
+ const uid_t kClearUid = 555;
+ const std::string kMsg = "constant log message";
+ const size_t kMaxSize =
+ (sizeof(SerializedLogEntry) + kMsg.size()) * SerializedLogBuffer::kChunkSizeDivisor * 2;
+
+ std::list<SerializedLogChunk> chunks;
+ std::vector<TestEntry> entries0 = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 1,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 2,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries0, true));
+ std::vector<TestEntry> entries1 = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 3,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 4,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries1, true));
+ std::vector<TestEntry> entries2 = {
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 5,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ {.uid = kClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 6,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries2, true));
+ std::vector<TestEntry> entries3 = {
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 7,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ {.uid = kNonClearUid,
+ .pid = 10,
+ .tid = 10,
+ .sequence = 8,
+ .realtime = log_time(0, 1),
+ .msg = kMsg},
+ };
+ chunks.emplace_back(CreateChunk(kMaxSize, entries3, false));
+
+ std::list<SerializedLogChunk> expected_chunks;
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, entries0, true));
+
+ std::vector<TestEntry> expected_entries_1;
+ expected_entries_1.emplace_back(entries1[0]);
+ expected_entries_1.emplace_back(entries3[0]);
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, expected_entries_1, true));
+
+ std::vector<TestEntry> expected_entries_2;
+ expected_entries_2.emplace_back(entries3[1]);
+ expected_chunks.emplace_back(CreateChunk(kMaxSize, expected_entries_2, false));
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+
+ ClearLogsByUid(chunks, kClearUid, kMaxSize, LOG_ID_MAIN, nullptr);
+ VerifyChunks(expected_chunks, chunks);
+}
diff --git a/logd/SerializedLogChunk.cpp b/logd/SerializedLogChunk.cpp
index a512bf3..e6b4f50 100644
--- a/logd/SerializedLogChunk.cpp
+++ b/logd/SerializedLogChunk.cpp
@@ -25,12 +25,16 @@
CHECK_EQ(reader_ref_count_, 0U);
}
-void SerializedLogChunk::Compress() {
+void SerializedLogChunk::FinishWriting() {
+ writer_active_ = false;
CHECK_EQ(compressed_log_.size(), 0U);
CompressionEngine::GetInstance().Compress(contents_, write_offset_, compressed_log_);
LOG(VERBOSE) << "Compressed Log, buffer max size: " << contents_.size()
<< " size used: " << write_offset_
<< " compressed size: " << compressed_log_.size();
+ if (reader_ref_count_ == 0) {
+ contents_.Resize(0);
+ }
}
// TODO: Develop a better reference counting strategy to guard against the case where the writer is
@@ -73,54 +77,6 @@
}
}
-bool SerializedLogChunk::ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats) {
- CHECK_EQ(reader_ref_count_, 0U);
- if (write_offset_ == 0) {
- return true;
- }
-
- IncReaderRefCount();
-
- int read_offset = 0;
- int new_write_offset = 0;
- while (read_offset < write_offset_) {
- const auto* entry = log_entry(read_offset);
- if (entry->uid() == uid) {
- read_offset += entry->total_len();
- if (stats != nullptr) {
- stats->Subtract(entry->ToLogStatisticsElement(log_id));
- }
- continue;
- }
- size_t entry_total_len = entry->total_len();
- if (read_offset != new_write_offset) {
- memmove(contents_.data() + new_write_offset, contents_.data() + read_offset,
- entry_total_len);
- }
- read_offset += entry_total_len;
- new_write_offset += entry_total_len;
- }
-
- if (new_write_offset == 0) {
- DecReaderRefCount();
- return true;
- }
-
- // Clear the old compressed logs and set write_offset_ appropriately to compress the new
- // partially cleared log.
- if (new_write_offset != write_offset_) {
- write_offset_ = new_write_offset;
- if (!writer_active_) {
- compressed_log_.Resize(0);
- Compress();
- }
- }
-
- DecReaderRefCount();
-
- return false;
-}
-
bool SerializedLogChunk::CanLog(size_t len) {
return write_offset_ + len <= contents_.size();
}
diff --git a/logd/SerializedLogChunk.h b/logd/SerializedLogChunk.h
index 2318a2d..eb41674 100644
--- a/logd/SerializedLogChunk.h
+++ b/logd/SerializedLogChunk.h
@@ -18,6 +18,7 @@
#include <sys/types.h>
+#include <list>
#include <vector>
#include <android-base/logging.h>
@@ -31,11 +32,34 @@
class SerializedLogChunk {
public:
+ friend void VerifyChunks(const std::list<SerializedLogChunk>& expected,
+ const std::list<SerializedLogChunk>& chunks);
+
+ class LogEntryIterator {
+ public:
+ LogEntryIterator(SerializedLogChunk& chunk, int read_offset_)
+ : chunk_(chunk), read_offset_(read_offset_) {}
+
+ LogEntryIterator& operator++() {
+ read_offset_ += chunk_.log_entry(read_offset_)->total_len();
+ return *this;
+ }
+
+ bool operator!=(const LogEntryIterator& other) const {
+ return read_offset_ != other.read_offset_;
+ }
+ const SerializedLogEntry& operator*() const { return *chunk_.log_entry(read_offset_); }
+
+ private:
+ SerializedLogChunk& chunk_;
+ int read_offset_;
+ };
+
explicit SerializedLogChunk(size_t size) : contents_(size) {}
SerializedLogChunk(SerializedLogChunk&& other) noexcept = default;
~SerializedLogChunk();
- void Compress();
+ void FinishWriting();
void IncReaderRefCount();
void DecReaderRefCount();
void AttachReader(SerializedFlushToState* reader);
@@ -43,9 +67,6 @@
void NotifyReadersOfPrune(log_id_t log_id) REQUIRES(logd_lock);
- // Must have no readers referencing this. Return true if there are no logs left in this chunk.
- bool ClearUidLogs(uid_t uid, log_id_t log_id, LogStatistics* stats);
-
bool CanLog(size_t len);
SerializedLogEntry* Log(uint64_t sequence, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
const char* msg, uint16_t len);
@@ -57,14 +78,6 @@
return sizeof(*this) + (compressed_log_.size() ?: contents_.size());
}
- void FinishWriting() {
- writer_active_ = false;
- Compress();
- if (reader_ref_count_ == 0) {
- contents_.Resize(0);
- }
- }
-
const SerializedLogEntry* log_entry(int offset) const {
CHECK(writer_active_ || reader_ref_count_ > 0);
return reinterpret_cast<const SerializedLogEntry*>(data() + offset);
@@ -73,6 +86,10 @@
int write_offset() const { return write_offset_; }
uint64_t highest_sequence_number() const { return highest_sequence_number_; }
+ LogEntryIterator begin() { return LogEntryIterator(*this, 0); }
+
+ LogEntryIterator end() { return LogEntryIterator(*this, write_offset_); }
+
// Exposed for testing
uint32_t reader_ref_count() const { return reader_ref_count_; }
diff --git a/logd/SerializedLogChunkTest.cpp b/logd/SerializedLogChunkTest.cpp
index d00ef5f..8e53365 100644
--- a/logd/SerializedLogChunkTest.cpp
+++ b/logd/SerializedLogChunkTest.cpp
@@ -115,192 +115,3 @@
EXPECT_DEATH({ chunk.DecReaderRefCount(); }, "");
}
-// Check that the CHECK() in ClearUidLogs() if the ref count is greater than 0 is caught.
-TEST_F(SerializedLogChunk_DeathTest, catch_ClearUidLogs_CHECK) {
- size_t chunk_size = 10 * 4096;
- auto chunk = SerializedLogChunk{chunk_size};
- chunk.IncReaderRefCount();
- EXPECT_DEATH({ chunk.ClearUidLogs(1000, LOG_ID_MAIN, nullptr); }, "");
- chunk.DecReaderRefCount();
-}
-
-class UidClearTest : public testing::TestWithParam<bool> {
- protected:
- template <typename Write, typename Check>
- void Test(const Write& write, const Check& check, bool expected_result) {
- write(chunk_);
-
- bool finish_writing = GetParam();
- if (finish_writing) {
- chunk_.FinishWriting();
- }
- EXPECT_EQ(expected_result, chunk_.ClearUidLogs(kUidToClear, LOG_ID_MAIN, nullptr));
- if (finish_writing) {
- chunk_.IncReaderRefCount();
- }
-
- check(chunk_);
-
- if (finish_writing) {
- chunk_.DecReaderRefCount();
- }
- }
-
- static constexpr size_t kChunkSize = 10 * 4096;
- static constexpr uid_t kUidToClear = 1000;
- static constexpr uid_t kOtherUid = 1234;
-
- SerializedLogChunk chunk_{kChunkSize};
-};
-
-// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
-TEST_P(UidClearTest, no_logs_in_chunk) {
- auto write = [](SerializedLogChunk&) {};
- auto check = [](SerializedLogChunk&) {};
-
- Test(write, check, true);
-}
-
-// Test that ClearUidLogs() is a no-op if there are no logs of that UID in the buffer.
-TEST_P(UidClearTest, no_logs_from_uid) {
- static const char msg[] = "this is a log message";
- auto write = [](SerializedLogChunk& chunk) {
- chunk.Log(1, log_time(), kOtherUid, 1, 2, msg, sizeof(msg));
- };
-
- auto check = [](SerializedLogChunk& chunk) {
- auto* entry = chunk.log_entry(0);
- EXPECT_STREQ(msg, entry->msg());
- };
-
- Test(write, check, false);
-}
-
-// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
-TEST_P(UidClearTest, all_single) {
- static const char msg[] = "this is a log message";
- auto write = [](SerializedLogChunk& chunk) {
- chunk.Log(1, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
- };
- auto check = [](SerializedLogChunk&) {};
-
- Test(write, check, true);
-}
-
-// Test that ClearUidLogs() returns true if all logs in a given buffer correspond to the given UID.
-TEST_P(UidClearTest, all_multiple) {
- static const char msg[] = "this is a log message";
- auto write = [](SerializedLogChunk& chunk) {
- chunk.Log(2, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
- chunk.Log(3, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
- chunk.Log(4, log_time(), kUidToClear, 1, 2, msg, sizeof(msg));
- };
- auto check = [](SerializedLogChunk&) {};
-
- Test(write, check, true);
-}
-
-static std::string MakePrintable(const uint8_t* in, size_t length) {
- std::string result;
- for (size_t i = 0; i < length; ++i) {
- uint8_t c = in[i];
- if (isprint(c)) {
- result.push_back(c);
- } else {
- result.append(StringPrintf("\\%02x", static_cast<int>(c) & 0xFF));
- }
- }
- return result;
-}
-
-// This test clears UID logs at the beginning and end of the buffer, as well as two back to back
-// logs in the interior.
-TEST_P(UidClearTest, clear_beginning_and_end) {
- static const char msg1[] = "this is a log message";
- static const char msg2[] = "non-cleared message";
- static const char msg3[] = "back to back cleared messages";
- static const char msg4[] = "second in a row gone";
- static const char msg5[] = "but we save this one";
- static const char msg6[] = "and this 1!";
- static const char msg7[] = "the last one goes too";
- auto write = [](SerializedLogChunk& chunk) {
- ASSERT_NE(nullptr, chunk.Log(1, log_time(), kUidToClear, 1, 2, msg1, sizeof(msg1)));
- ASSERT_NE(nullptr, chunk.Log(2, log_time(), kOtherUid, 1, 2, msg2, sizeof(msg2)));
- ASSERT_NE(nullptr, chunk.Log(3, log_time(), kUidToClear, 1, 2, msg3, sizeof(msg3)));
- ASSERT_NE(nullptr, chunk.Log(4, log_time(), kUidToClear, 1, 2, msg4, sizeof(msg4)));
- ASSERT_NE(nullptr, chunk.Log(5, log_time(), kOtherUid, 1, 2, msg5, sizeof(msg5)));
- ASSERT_NE(nullptr, chunk.Log(6, log_time(), kOtherUid, 1, 2, msg6, sizeof(msg6)));
- ASSERT_NE(nullptr, chunk.Log(7, log_time(), kUidToClear, 1, 2, msg7, sizeof(msg7)));
- };
-
- auto check = [](SerializedLogChunk& chunk) {
- size_t read_offset = 0;
- auto* entry = chunk.log_entry(read_offset);
- EXPECT_STREQ(msg2, entry->msg());
- read_offset += entry->total_len();
-
- entry = chunk.log_entry(read_offset);
- EXPECT_STREQ(msg5, entry->msg());
- read_offset += entry->total_len();
-
- entry = chunk.log_entry(read_offset);
- EXPECT_STREQ(msg6, entry->msg()) << MakePrintable(chunk.data(), chunk.write_offset());
- read_offset += entry->total_len();
-
- EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
- };
- Test(write, check, false);
-}
-
-// This tests the opposite case of beginning_and_end, in which we don't clear the beginning or end
-// logs. There is a single log pruned in the middle instead of back to back logs.
-TEST_P(UidClearTest, save_beginning_and_end) {
- static const char msg1[] = "saved first message";
- static const char msg2[] = "cleared interior message";
- static const char msg3[] = "last message stays";
- auto write = [](SerializedLogChunk& chunk) {
- ASSERT_NE(nullptr, chunk.Log(1, log_time(), kOtherUid, 1, 2, msg1, sizeof(msg1)));
- ASSERT_NE(nullptr, chunk.Log(2, log_time(), kUidToClear, 1, 2, msg2, sizeof(msg2)));
- ASSERT_NE(nullptr, chunk.Log(3, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
- };
-
- auto check = [](SerializedLogChunk& chunk) {
- size_t read_offset = 0;
- auto* entry = chunk.log_entry(read_offset);
- EXPECT_STREQ(msg1, entry->msg());
- read_offset += entry->total_len();
-
- entry = chunk.log_entry(read_offset);
- EXPECT_STREQ(msg3, entry->msg());
- read_offset += entry->total_len();
-
- EXPECT_EQ(static_cast<int>(read_offset), chunk.write_offset());
- };
- Test(write, check, false);
-}
-
-INSTANTIATE_TEST_CASE_P(UidClearTests, UidClearTest, testing::Values(true, false));
-
-// b/166187079: ClearUidLogs() should not compress the log if writer_active_ is true
-TEST(SerializedLogChunk, ClearUidLogs_then_FinishWriting) {
- static constexpr size_t kChunkSize = 10 * 4096;
- static constexpr uid_t kUidToClear = 1000;
- static constexpr uid_t kOtherUid = 1234;
-
- SerializedLogChunk chunk{kChunkSize};
- static const char msg1[] = "saved first message";
- static const char msg2[] = "cleared interior message";
- static const char msg3[] = "last message stays";
- ASSERT_NE(nullptr, chunk.Log(1, log_time(), kOtherUid, 1, 2, msg1, sizeof(msg1)));
- ASSERT_NE(nullptr, chunk.Log(2, log_time(), kUidToClear, 1, 2, msg2, sizeof(msg2)));
- ASSERT_NE(nullptr, chunk.Log(3, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
-
- chunk.ClearUidLogs(kUidToClear, LOG_ID_MAIN, nullptr);
-
- ASSERT_NE(nullptr, chunk.Log(4, log_time(), kOtherUid, 1, 2, msg3, sizeof(msg3)));
-
- chunk.FinishWriting();
- // The next line would violate a CHECK() during decompression with the faulty code.
- chunk.IncReaderRefCount();
- chunk.DecReaderRefCount();
-}