Implement log buffer essential methods
Also add unit tests for the log buffer implementation.
Bug: 146164384
Test: run_tests.sh --gtest_filter='LogBuffer.*'
Change-Id: I99a516940e24b3be97a60cd341529fefbed5fee4
diff --git a/platform/platform.mk b/platform/platform.mk
index 70fb58c..aaf7c21 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -436,3 +436,5 @@
GOOGLETEST_COMMON_SRCS += platform/linux/assert.cc
GOOGLETEST_COMMON_SRCS += platform/linux/audio_source.cc
GOOGLETEST_COMMON_SRCS += platform/linux/platform_audio.cc
+GOOGLETEST_COMMON_SRCS += platform/test/log_buffer_test.cc
+GOOGLETEST_COMMON_SRCS += platform/shared/log_buffer.cc
diff --git a/platform/shared/include/chre/platform/shared/log_buffer.h b/platform/shared/include/chre/platform/shared/log_buffer.h
index 0bc8eb6..e30872c 100644
--- a/platform/shared/include/chre/platform/shared/log_buffer.h
+++ b/platform/shared/include/chre/platform/shared/log_buffer.h
@@ -19,7 +19,9 @@
#include <cinttypes>
#include <cstdarg>
+#include <cstring>
+#include "chre/platform/mutex.h"
#include "chre/util/fixed_size_vector.h"
#include "chre/util/singleton.h"
@@ -36,12 +38,12 @@
* THRESHOLD - The LogBuffer should notify the platform when a certain thresold
* of memory has been allocated for logs in the buffer.
*/
-enum LogBufferNotificationSetting { ALWAYS, NEVER, THRESHOLD };
+enum class LogBufferNotificationSetting : uint8_t { ALWAYS, NEVER, THRESHOLD };
/**
* The log level options for logs stored in a log buffer.
*/
-enum LogBufferLogLevel { ERROR, WARN, INFO, DEBUG, VERBOSE };
+enum class LogBufferLogLevel : uint8_t { ERROR, WARN, INFO, DEBUG, VERBOSE };
// Forward declaration for LogBufferCallbackInterface.
class LogBuffer;
@@ -52,6 +54,7 @@
* through this callback interface.
*/
class LogBufferCallbackInterface {
+ public:
/**
* Notify the platform code that is using the buffer manager that it should
* call copyLogs because the buffer internal state has changed to suit the
@@ -75,7 +78,8 @@
* the state of the log buffer.
* @param buffer The buffer location that will store log data.
* message.
- * @param bufferSize The number of bytes in the buffer.
+ * @param bufferSize The number of bytes in the buffer. This value must be >
+ * kBufferMinSize
*/
LogBuffer(LogBufferCallbackInterface *callback, void *buffer,
size_t bufferSize);
@@ -133,6 +137,95 @@
*/
void updateNotificationSetting(LogBufferNotificationSetting setting,
size_t thresholdBytes = 0);
+
+ private:
+ /**
+ * @return The current buffer size.
+ */
+ size_t getBufferSize() const;
+
+ /**
+ * Increment the value and take the modulus of the max size of the buffer.
+ *
+ * @param originalVal The original value to increment and mod.
+ * @param incrementBy The amount to increment by.
+ * @return The final value after incrementing and modulus.
+ */
+ size_t incrementAndModByBufferMaxSize(size_t originalVal,
+ size_t incrementBy) const;
+
+ /**
+ * Copy from the source memory location to the buffer data ensuring that
+ * the copy wraps around the buffer data if needed.
+ *
+ * @param size The number of bytes to copy into the buffer.
+ * @param source The memory location to copy from.
+ */
+ void copyToBuffer(size_t size, const void *source);
+
+ /**
+ * Copy from the buffer data to a destination memory location ensuring that
+ * the copy wraps around the buffer data if needed.
+ *
+ * @param size The number of bytes to copy into the buffer.
+ * @param destination The memory location to copy to.
+ */
+ void copyFromBuffer(size_t size, void *destination);
+
+ /**
+ * Get next index indicating the start of a log entry from the starting
+ * index of a previous log entry.
+ *
+ * @param startingIndex The starting index given.
+ * @param logSize Non-null pointer that will be set to the size of the current
+ * log message.
+ * @return The next starting log index.
+ */
+ size_t getNextLogIndex(size_t startingIndex, size_t *logSize);
+
+ //! The number of bytes in a log entry of the buffer before the log size data
+ //! is encountered.
+ static constexpr size_t kLogSizeOffset = 5;
+ //! The number of bytes that the log size data takes up in a log entry.
+ static constexpr size_t kLogSizeBytes = 1;
+ //! The max size of a single log entry which must fit in a single byte.
+ static constexpr size_t kLogMaxSize = 255;
+
+ /**
+ * The buffer data is stored in the format
+ *
+ * [ logLevel (1B) , timestamp (4B), dataLength (1B), data (dataLenB) ]
+ *
+ * Since dataLength cannot be greater than uint8_t the max size of the data
+ * portion can be max 255.
+ */
+ uint8_t *mBufferData;
+
+ // TODO(b/170870354): Create a cirular buffer class to reuse this concept in
+ // other parts of CHRE
+ //! The buffer data head index
+ size_t mBufferDataHeadIndex = 0;
+ //! The buffer data tail index
+ size_t mBufferDataTailIndex = 0;
+ //! The current size of the data buffer
+ size_t mBufferDataSize = 0;
+ //! The buffer max size
+ size_t mBufferMaxSize;
+ //! The buffer min size
+ // TODO(b/170870354): Setup a more appropriate min size
+ static constexpr size_t kBufferMinSize = 1024; // 1KB
+
+ //! The callback object
+ LogBufferCallbackInterface *mCallback;
+ //! The notification setting object
+ LogBufferNotificationSetting mNotificationSetting =
+ LogBufferNotificationSetting::NEVER;
+ //! The number of bytes that will trigger the threshold notification
+ size_t mNotificationThresholdBytes = 0;
+
+ // TODO(srok): Optimize the locking scheme
+ //! The mutex guarding the buffer data bytes array
+ Mutex mBufferDataLock;
};
} // namespace chre
diff --git a/platform/shared/log_buffer.cc b/platform/shared/log_buffer.cc
index 246d513..16be3e5 100644
--- a/platform/shared/log_buffer.cc
+++ b/platform/shared/log_buffer.cc
@@ -15,6 +15,7 @@
*/
#include "chre/platform/shared/log_buffer.h"
+#include "chre/util/lock_guard.h"
#include <cstdarg>
#include <cstdio>
@@ -22,27 +23,134 @@
namespace chre {
LogBuffer::LogBuffer(LogBufferCallbackInterface *callback, void *buffer,
- size_t bufferSize) {
- // TODO(b/146164384): Implement
+ size_t bufferSize)
+ : mBufferData(static_cast<uint8_t *>(buffer)),
+ mBufferMaxSize(bufferSize),
+ mCallback(callback) {
+ CHRE_ASSERT(bufferSize >= kBufferMinSize);
}
void LogBuffer::handleLog(LogBufferLogLevel logLevel, uint32_t timestampMs,
const char *log) {
- // TODO(b/146164384): Implement
+ uint8_t logLen = static_cast<uint8_t>(
+ strnlen(log, kLogMaxSize - kLogSizeOffset - kLogSizeBytes));
+ size_t totalLogSize = kLogSizeOffset + kLogSizeBytes + logLen;
+
+ if (totalLogSize > mBufferMaxSize) {
+ return;
+ }
+
+ {
+ LockGuard<Mutex> lockGuard(mBufferDataLock);
+ // Invalidate memory allocated for log at head while the buffer is greater
+ // than max size
+ while (getBufferSize() + totalLogSize > mBufferMaxSize) {
+ size_t logSize;
+ mBufferDataHeadIndex = getNextLogIndex(mBufferDataHeadIndex, &logSize);
+ mBufferDataSize -= logSize;
+ }
+ copyToBuffer(sizeof(logLevel), &logLevel);
+ copyToBuffer(sizeof(timestampMs), ×tampMs);
+ copyToBuffer(sizeof(logLen), &logLen);
+ copyToBuffer(logLen, reinterpret_cast<const void *>(log));
+ }
+
+ switch (mNotificationSetting) {
+ case LogBufferNotificationSetting::ALWAYS: {
+ mCallback->onLogsReady(this);
+ break;
+ }
+ case LogBufferNotificationSetting::NEVER: {
+ break;
+ }
+ case LogBufferNotificationSetting::THRESHOLD: {
+ if (getBufferSize() > mNotificationThresholdBytes) {
+ mCallback->onLogsReady(this);
+ }
+ break;
+ }
+ }
}
size_t LogBuffer::copyLogs(void *destination, size_t size) {
- // TODO(b/146164384): Implement
- return 0;
+ LockGuard<Mutex> lock(mBufferDataLock);
+
+ size_t copySize = 0;
+
+ if (size != 0 && destination != nullptr && getBufferSize() != 0) {
+ size_t logStartIndex = mBufferDataHeadIndex;
+ size_t logDataSize = mBufferData[incrementAndModByBufferMaxSize(
+ logStartIndex, kLogSizeOffset)];
+ size_t logSize = kLogSizeOffset + kLogSizeBytes + logDataSize;
+ size_t sizeAfterAddingLog = logSize;
+ while (sizeAfterAddingLog <= size &&
+ sizeAfterAddingLog <= getBufferSize()) {
+ logStartIndex = getNextLogIndex(logStartIndex, &logSize);
+ sizeAfterAddingLog += logSize;
+ }
+ copySize = sizeAfterAddingLog - logSize;
+ copyFromBuffer(copySize, destination);
+ mBufferDataHeadIndex = logStartIndex;
+ }
+
+ return copySize;
}
void LogBuffer::transferTo(LogBuffer &buffer) {
// TODO(b/146164384): Implement
}
-void updateNotificationSetting(LogBufferNotificationSetting setting,
- size_t thresholdBytes) {
- // TODO(b/146164384): Implement
+void LogBuffer::updateNotificationSetting(LogBufferNotificationSetting setting,
+ size_t thresholdBytes) {
+ LockGuard<Mutex> lock(mBufferDataLock);
+
+ mNotificationSetting = setting;
+ mNotificationThresholdBytes = thresholdBytes;
+}
+
+size_t LogBuffer::getBufferSize() const {
+ return mBufferDataSize;
+}
+
+size_t LogBuffer::incrementAndModByBufferMaxSize(size_t originalVal,
+ size_t incrementBy) const {
+ return (originalVal + incrementBy) % mBufferMaxSize;
+}
+
+void LogBuffer::copyToBuffer(size_t size, const void *source) {
+ const uint8_t *sourceBytes = static_cast<const uint8_t *>(source);
+ if (mBufferDataTailIndex + size > mBufferMaxSize) {
+ size_t firstSize = mBufferMaxSize - mBufferDataTailIndex;
+ size_t secondSize = size - firstSize;
+ memcpy(&mBufferData[mBufferDataTailIndex], sourceBytes, firstSize);
+ memcpy(mBufferData, &sourceBytes[firstSize], secondSize);
+ } else {
+ memcpy(&mBufferData[mBufferDataTailIndex], sourceBytes, size);
+ }
+ mBufferDataSize += size;
+ mBufferDataTailIndex =
+ incrementAndModByBufferMaxSize(mBufferDataTailIndex, size);
+}
+
+void LogBuffer::copyFromBuffer(size_t size, void *destination) {
+ uint8_t *destinationBytes = static_cast<uint8_t *>(destination);
+ if (mBufferDataHeadIndex + size > mBufferMaxSize) {
+ size_t firstSize = mBufferMaxSize - mBufferDataHeadIndex;
+ size_t secondSize = size - firstSize;
+ memcpy(destinationBytes, &mBufferData[mBufferDataHeadIndex], firstSize);
+ memcpy(&destinationBytes[firstSize], mBufferData, secondSize);
+ } else {
+ memcpy(destinationBytes, &mBufferData[mBufferDataHeadIndex], size);
+ }
+ mBufferDataSize -= size;
+ mBufferDataHeadIndex =
+ incrementAndModByBufferMaxSize(mBufferDataHeadIndex, size);
+}
+
+size_t LogBuffer::getNextLogIndex(size_t startingIndex, size_t *logSize) {
+ size_t logDataSize = mBufferData[startingIndex + kLogSizeOffset];
+ *logSize = kLogSizeOffset + kLogSizeBytes + logDataSize;
+ return incrementAndModByBufferMaxSize(startingIndex, *logSize);
}
} // namespace chre
diff --git a/platform/test/log_buffer_test.cc b/platform/test/log_buffer_test.cc
new file mode 100644
index 0000000..41b3b95
--- /dev/null
+++ b/platform/test/log_buffer_test.cc
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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 <gtest/gtest.h>
+#include <string>
+
+#include "chre/platform/atomic.h"
+#include "chre/platform/condition_variable.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/log_buffer.h"
+
+namespace chre {
+
+// TODO(b/146164384): Test that the onLogsReady callback is called
+// asynchronously
+
+class TestLogBufferCallback : public LogBufferCallbackInterface {
+ public:
+ void onLogsReady(LogBuffer *logBuffer) {
+ // Do nothing
+ }
+};
+
+static constexpr size_t kDefaultBufferSize = 1024;
+static constexpr size_t kBytesBeforeLogData = 6;
+
+TEST(LogBuffer, HandleOneLogAndCopy) {
+ char buffer[kDefaultBufferSize];
+ constexpr size_t kOutBufferSize = 20;
+ char outBuffer[kOutBufferSize];
+ const char *testLogStr = "test";
+ char testedBuffer[kOutBufferSize];
+ TestLogBufferCallback callback;
+
+ LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+ logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+ size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+ EXPECT_EQ(bytesCopied,
+ strlen(testLogStr) +
+ kBytesBeforeLogData /*loglevel, timestamp, and loglen*/);
+ memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, strlen(testLogStr));
+ testedBuffer[strlen(testLogStr)] = '\0';
+ EXPECT_TRUE(strcmp(testedBuffer, testLogStr) == 0);
+}
+
+TEST(LogBuffer, FailOnMoreCopyThanHandle) {
+ char buffer[kDefaultBufferSize];
+ constexpr size_t kOutBufferSize = 20;
+ char outBuffer[kOutBufferSize];
+ constexpr size_t kEmptyBufferSize = 10;
+ char emptyBuffer[kEmptyBufferSize];
+ emptyBuffer[0] = '\0';
+ const char *testLogStr = "test";
+ TestLogBufferCallback callback;
+ char testedBuffer[kOutBufferSize];
+
+ LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+ logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+ logBuffer.copyLogs(outBuffer, kOutBufferSize);
+ memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, strlen(testLogStr));
+ testedBuffer[strlen(testLogStr)] = '\0';
+ size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+ EXPECT_EQ(bytesCopied, 0);
+ EXPECT_EQ(strlen(emptyBuffer), 0);
+}
+
+TEST(LogBuffer, FailOnHandleLargerLogThanBufferSize) {
+ char buffer[kDefaultBufferSize];
+ constexpr size_t kOutBufferSize = 20;
+ char outBuffer[kOutBufferSize];
+ // Note the size of this log is too big to fit in the buffer that we are
+ // using for the LogBuffer object
+ std::string testLogStrStr(1025, 'a');
+ TestLogBufferCallback callback;
+
+ LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+ logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStrStr.c_str());
+ size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+ // Should not be able to read this log out because there should be no log in
+ // the first place
+ EXPECT_EQ(bytesCopied, 0);
+}
+
+TEST(LogBuffer, LogOverwritten) {
+ char buffer[kDefaultBufferSize];
+ constexpr size_t kOutBufferSize = 200;
+ char outBuffer[kOutBufferSize];
+ char testedBuffer[kOutBufferSize];
+ TestLogBufferCallback callback;
+ LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+ // This for loop adds 1060 bytes of data through the buffer which is > than
+ // 1024
+ for (size_t i = 0; i < 10; i++) {
+ std::string testLogStrStr(100, 'a' + i);
+ const char *testLogStr = testLogStrStr.c_str();
+ logBuffer.handleLog(LogBufferLogLevel::INFO, 0, testLogStr);
+ }
+ size_t bytesCopied = logBuffer.copyLogs(outBuffer, kBytesBeforeLogData + 100);
+ memcpy(testedBuffer, outBuffer + kBytesBeforeLogData, 100);
+ testedBuffer[100] = '\0';
+
+ // Should have read out the second from front test log string which is 'a' + 1
+ // = 'b'
+ EXPECT_TRUE(strcmp(testedBuffer, std::string(100, 'b').c_str()) == 0);
+ EXPECT_EQ(bytesCopied, kBytesBeforeLogData + 100);
+}
+
+TEST(LogBuffer, CopyIntoEmptyBuffer) {
+ char buffer[kDefaultBufferSize];
+ constexpr size_t kOutBufferSize = 0;
+ char outBuffer[kOutBufferSize];
+ TestLogBufferCallback callback;
+ LogBuffer logBuffer(&callback, buffer, kDefaultBufferSize);
+
+ logBuffer.handleLog(LogBufferLogLevel::INFO, 0, "test");
+ size_t bytesCopied = logBuffer.copyLogs(outBuffer, kOutBufferSize);
+
+ EXPECT_EQ(bytesCopied, 0);
+}
+
+// TODO(srok): Add multithreaded tests
+
+} // namespace chre