blob: 43a06ab152f4766bb73ca4971f7df924f25b71c6 [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.
*/
#ifndef SRC_BASE_LOG_RING_BUFFER_H_
#define SRC_BASE_LOG_RING_BUFFER_H_
#include <stddef.h>
#include <stdio.h>
#include <array>
#include <atomic>
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/thread_annotations.h"
namespace perfetto {
namespace base {
// Defined out of line because a static constexpr requires static storage if
// ODR-used, not worth adding a .cc file just for tests.
constexpr size_t kLogRingBufEntries = 8;
constexpr size_t kLogRingBufMsgLen = 256;
// A static non-allocating ring-buffer to hold the most recent log events.
// This class is really an implementation detail of logging.cc. The only reason
// why is fully defined in a dedicated header is for allowing unittesting,
// without leaking extra headers into logging.h (which is a high-fanout header).
// This is used to report the last logs in a crash report when a CHECK/FATAL
// is encountered.
// This class has just an Append() method to insert events into the buffer and
// a Read() to read the events in FIFO order. Read() is non-destructive.
//
// Thread safety considerations:
// - The Append() method can be called concurrently by several threads, unless
// there are > kLogRingBufEntries concurrent threads. Even if that happens,
// case some events will contain a mix of strings but the behavior of
// futher Append() and Read() is still defined.
// - The Read() method is not thread safe but it's fine in practice. Even if
// it's called concurrently with other Append(), it only causes some partial
// events to be emitted in output.
// In both cases, we never rely purely on \0, all operations are size-bound.
//
// See logging_unittest.cc for tests.
class LogRingBuffer {
public:
LogRingBuffer() = default;
LogRingBuffer(const LogRingBuffer&) = delete;
LogRingBuffer& operator=(const LogRingBuffer&) = delete;
LogRingBuffer(LogRingBuffer&&) = delete;
LogRingBuffer& operator=(LogRingBuffer&&) = delete;
// This takes three arguments because it fits its only caller (logging.cc).
// The args are just concatenated together (plus one space before the msg).
void Append(StringView tstamp, StringView source, StringView log_msg) {
// Reserve atomically a slot in the ring buffer, so any concurrent Append()
// won't overlap (unless too many concurrent Append() happen together).
// There is no strict synchronization here, |event_slot_| is atomic only for
// the sake of avoiding colliding on the same slot but does NOT guarantee
// full consistency and integrity of the log messages written in each slot.
// A release-store (or acq+rel) won't be enough for full consistency. Two
// threads that race on Append() and take the N+1 and N+2 slots could finish
// the write in reverse order. So Read() would need to synchronize with
// something else (either a per-slot atomic flag or with a second atomic
// counter which is incremented after the snprintf). Both options increase
// the cost of Append() with no huge benefits (90% of the perfetto services
// where we use it is single thread, and the log ring buffer is disabled
// on non-standalone builds like the SDK).
uint32_t slot = event_slot_.fetch_add(1, std::memory_order_relaxed);
slot = slot % kLogRingBufEntries;
char* const msg = events_[slot];
PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(msg, kLogRingBufMsgLen,
"see comments in log_ring_buffer.h")
snprintf(msg, kLogRingBufMsgLen, "%.*s%.*s %.*s",
static_cast<int>(tstamp.size()), tstamp.data(),
static_cast<int>(source.size()), source.data(),
static_cast<int>(log_msg.size()), log_msg.data());
}
// Reads back the buffer in FIFO order, up to |len - 1| characters at most
// (the -1 is because a NUL terminator is always appended, unless |len| == 0).
// The string written in |dst| is guaranteed to be NUL-terminated, even if
// |len| < buffer contents length.
// Returns the number of bytes written in output, excluding the \0 terminator.
size_t Read(char* dst, size_t len) {
if (len == 0)
return 0;
// This is a relaxed-load because we don't need to fully synchronize on the
// writing path for the reasons described in the fetch_add() above.
const uint32_t event_slot = event_slot_.load(std::memory_order_relaxed);
size_t dst_written = 0;
for (uint32_t pos = 0; pos < kLogRingBufEntries; ++pos) {
const uint32_t slot = (event_slot + pos) % kLogRingBufEntries;
const char* src = events_[slot];
if (*src == '\0')
continue; // Empty slot. Skip.
char* const wptr = dst + dst_written;
// |src| might not be null terminated. This can happen if some
// thread-race happened. Limit the copy length.
const size_t limit = std::min(len - dst_written, kLogRingBufMsgLen);
for (size_t i = 0; i < limit; ++i) {
const char c = src[i];
++dst_written;
if (c == '\0' || i == limit - 1) {
wptr[i] = '\n';
break;
}
// Skip non-printable ASCII characters to avoid confusing crash reports.
// Note that this deliberately mangles \n. Log messages should not have
// a \n in the middle and are NOT \n terminated. The trailing \n between
// each line is appended by the if () branch above.
const bool is_printable = c >= ' ' && c <= '~';
wptr[i] = is_printable ? c : '?';
}
}
// Ensure that the output string is null-terminated.
PERFETTO_DCHECK(dst_written <= len);
if (dst_written == len) {
// In case of truncation we replace the last char with \0. But the return
// value is the number of chars without \0, hence the --.
dst[--dst_written] = '\0';
} else {
dst[dst_written] = '\0';
}
return dst_written;
}
private:
using EventBuf = char[kLogRingBufMsgLen];
EventBuf events_[kLogRingBufEntries]{};
static_assert((kLogRingBufEntries & (kLogRingBufEntries - 1)) == 0,
"kLogRingBufEntries must be a power of two");
// A monotonically increasing counter incremented on each event written.
// It determines which of the kLogRingBufEntries indexes in |events_| should
// be used next.
// It grows >> kLogRingBufEntries, it's supposed to be always used
// mod(kLogRingBufEntries). A static_assert in the .cc file ensures that
// kLogRingBufEntries is a power of two so wraps are aligned.
std::atomic<uint32_t> event_slot_{};
};
} // namespace base
} // namespace perfetto
#endif // SRC_BASE_LOG_RING_BUFFER_H_