/*
* Copyright (C) 2013 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.
*/
// Non-blocking event logger intended for safe communication between processes via shared memory
#ifndef ANDROID_MEDIA_NBLOG_H
#define ANDROID_MEDIA_NBLOG_H
#include <binder/IMemory.h>
#include <audio_utils/fifo.h>
#include <utils/Mutex.h>
#include <utils/threads.h>
#include <vector>
namespace android {
class String8;
class NBLog {
public:
class Writer;
class Reader;
private:
enum Event {
EVENT_RESERVED,
EVENT_STRING, // ASCII string, not NUL-terminated
// TODO: make timestamp optional
EVENT_TIMESTAMP, // clock_gettime(CLOCK_MONOTONIC)
EVENT_INTEGER, // integer value entry
EVENT_FLOAT, // floating point value entry
EVENT_PID, // process ID and process name
EVENT_AUTHOR, // author index (present in merged logs) tracks entry's original log
EVENT_START_FMT, // logFormat start event: entry includes format string, following
// entries contain format arguments
EVENT_END_FMT, // end of logFormat argument list
};
// ---------------------------------------------------------------------------
// API for handling format entry operations
// a formatted entry has the following structure:
// * START_FMT entry, containing the format string
// * TIMESTAMP entry
// * author entry of the thread that generated it (optional, present in merged log)
// * format arg1
// * format arg2
// * ...
// * END_FMT entry
class FormatEntry {
public:
// build a Format Entry starting in the given pointer
class iterator;
explicit FormatEntry(const uint8_t *entry);
explicit FormatEntry(const iterator &it);
// entry representation in memory
struct entry {
const uint8_t type;
const uint8_t length;
const uint8_t data[0];
};
// entry tail representation (after data)
struct ending {
uint8_t length;
uint8_t next[0];
};
// entry iterator
class iterator {
public:
iterator();
iterator(const uint8_t *entry);
iterator(const iterator &other);
// dereference underlying entry
const entry& operator*() const;
const entry* operator->() const;
// advance to next entry
iterator& operator++(); // ++i
// back to previous entry
iterator& operator--(); // --i
iterator next() const;
iterator prev() const;
bool operator!=(const iterator &other) const;
int operator-(const iterator &other) const;
bool hasConsistentLength() const;
void copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
void copyData(uint8_t *dst) const;
template<typename T>
inline const T& payload() {
return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
}
private:
friend class FormatEntry;
const uint8_t *ptr;
};
// Entry's format string
const char* formatString() const;
// Enrty's format string length
size_t formatStringLength() const;
// Format arguments (excluding format string, timestamp and author)
iterator args() const;
// get format entry timestamp
timespec timestamp() const;
// entry's author index (-1 if none present)
// a Merger has a vector of Readers, author simply points to the index of the
// Reader that originated the entry
int author() const;
// copy entry, adding author before timestamp, returns size of original entry
iterator copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const;
iterator begin() const;
private:
// copies ordinary entry from src to dst, and returns length of entry
// size_t copyEntry(audio_utils_fifo_writer *dst, const iterator &it);
const uint8_t *mEntry;
};
// ---------------------------------------------------------------------------
// representation of a single log entry in private memory
struct Entry {
Entry(Event event, const void *data, size_t length)
: mEvent(event), mLength(length), mData(data) { }
/*virtual*/ ~Entry() { }
int readAt(size_t offset) const;
private:
friend class Writer;
Event mEvent; // event type
uint8_t mLength; // length of additional data, 0 <= mLength <= kMaxLength
const void *mData; // event type-specific data
static const size_t kMaxLength = 255;
public:
// mEvent, mLength, mData[...], duplicate mLength
static const size_t kOverhead = sizeof(FormatEntry::entry) + sizeof(FormatEntry::ending);
// endind length of previous entry
static const size_t kPreviousLengthOffset = - sizeof(FormatEntry::ending) +
offsetof(FormatEntry::ending, length);
};
// representation of a single log entry in shared memory
// byte[0] mEvent
// byte[1] mLength
// byte[2] mData[0]
// ...
// byte[2+i] mData[i]
// ...
// byte[2+mLength-1] mData[mLength-1]
// byte[2+mLength] duplicate copy of mLength to permit reverse scan
// byte[3+mLength] start of next log entry
static void appendInt(String8 *body, const void *data);
static void appendFloat(String8 *body, const void *data);
static void appendPID(String8 *body, const void *data, size_t length);
static void appendTimestamp(String8 *body, const void *data);
static size_t fmtEntryLength(const uint8_t *data);
public:
// Located in shared memory, must be POD.
// Exactly one process must explicitly call the constructor or use placement new.
// Since this is a POD, the destructor is empty and unnecessary to call it explicitly.
struct Shared {
Shared() /* mRear initialized via default constructor */ { }
/*virtual*/ ~Shared() { }
audio_utils_fifo_index mRear; // index one byte past the end of most recent Entry
char mBuffer[0]; // circular buffer for entries
};
public:
// ---------------------------------------------------------------------------
// FIXME Timeline was intended to wrap Writer and Reader, but isn't actually used yet.
// For now it is just a namespace for sharedSize().
class Timeline : public RefBase {
public:
#if 0
Timeline(size_t size, void *shared = NULL);
virtual ~Timeline();
#endif
// Input parameter 'size' is the desired size of the timeline in byte units.
// Returns the size rounded up to a power-of-2, plus the constant size overhead for indices.
static size_t sharedSize(size_t size);
#if 0
private:
friend class Writer;
friend class Reader;
const size_t mSize; // circular buffer size in bytes, must be a power of 2
bool mOwn; // whether I own the memory at mShared
Shared* const mShared; // pointer to shared memory
#endif
};
// ---------------------------------------------------------------------------
// Writer is thread-safe with respect to Reader, but not with respect to multiple threads
// calling Writer methods. If you need multi-thread safety for writing, use LockedWriter.
class Writer : public RefBase {
public:
Writer(); // dummy nop implementation without shared memory
// Input parameter 'size' is the desired size of the timeline in byte units.
// The size of the shared memory must be at least Timeline::sharedSize(size).
Writer(void *shared, size_t size);
Writer(const sp<IMemory>& iMemory, size_t size);
virtual ~Writer();
virtual void log(const char *string);
virtual void logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
virtual void logvf(const char *fmt, va_list ap);
virtual void logTimestamp();
virtual void logTimestamp(const struct timespec &ts);
virtual void logInteger(const int x);
virtual void logFloat(const float x);
virtual void logPID();
virtual void logFormat(const char *fmt, ...);
virtual void logVFormat(const char *fmt, va_list ap);
virtual void logStart(const char *fmt);
virtual void logEnd();
virtual bool isEnabled() const;
// return value for all of these is the previous isEnabled()
virtual bool setEnabled(bool enabled); // but won't enable if no shared memory
bool enable() { return setEnabled(true); }
bool disable() { return setEnabled(false); }
sp<IMemory> getIMemory() const { return mIMemory; }
private:
// 0 <= length <= kMaxLength
void log(Event event, const void *data, size_t length);
void log(const Entry *entry, bool trusted = false);
Shared* const mShared; // raw pointer to shared memory
sp<IMemory> mIMemory; // ref-counted version, initialized in constructor and then const
audio_utils_fifo * const mFifo; // FIFO itself,
// non-NULL unless constructor fails
audio_utils_fifo_writer * const mFifoWriter; // used to write to FIFO,
// non-NULL unless dummy constructor used
bool mEnabled; // whether to actually log
// cached pid and process name to use in %p format specifier
// total tag length is mPidTagSize and process name is not zero terminated
char *mPidTag;
size_t mPidTagSize;
};
// ---------------------------------------------------------------------------
// Similar to Writer, but safe for multiple threads to call concurrently
class LockedWriter : public Writer {
public:
LockedWriter();
LockedWriter(void *shared, size_t size);
virtual void log(const char *string);
virtual void logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
virtual void logvf(const char *fmt, va_list ap);
virtual void logTimestamp();
virtual void logTimestamp(const struct timespec &ts);
virtual void logInteger(const int x);
virtual void logFloat(const float x);
virtual void logPID();
virtual void logStart(const char *fmt);
virtual void logEnd();
virtual bool isEnabled() const;
virtual bool setEnabled(bool enabled);
private:
mutable Mutex mLock;
};
// ---------------------------------------------------------------------------
class Reader : public RefBase {
public:
// A snapshot of a readers buffer
class Snapshot {
public:
Snapshot() : mData(NULL), mLost(0) {}
Snapshot(size_t bufferSize) : mData(new uint8_t[bufferSize]) {}
~Snapshot() { delete[] mData; }
// copy of the buffer
uint8_t *data() const { return mData; }
// amount of data lost (given by audio_utils_fifo_reader)
size_t lost() const { return mLost; }
// iterator to beginning of readable segment of snapshot
// data between begin and end has valid entries
FormatEntry::iterator begin() { return mBegin; }
// iterator to end of readable segment of snapshot
FormatEntry::iterator end() { return mEnd; }
private:
friend class Reader;
uint8_t *mData;
size_t mLost;
FormatEntry::iterator mBegin;
FormatEntry::iterator mEnd;
};
// Input parameter 'size' is the desired size of the timeline in byte units.
// The size of the shared memory must be at least Timeline::sharedSize(size).
Reader(const void *shared, size_t size);
Reader(const sp<IMemory>& iMemory, size_t size);
virtual ~Reader();
// get snapshot of readers fifo buffer, effectively consuming the buffer
std::unique_ptr<Snapshot> getSnapshot();
// dump a particular snapshot of the reader
void dump(int fd, size_t indent, Snapshot & snap);
// dump the current content of the reader's buffer
void dump(int fd, size_t indent = 0);
bool isIMemory(const sp<IMemory>& iMemory) const;
private:
/*const*/ Shared* const mShared; // raw pointer to shared memory, actually const but not
// declared as const because audio_utils_fifo() constructor
sp<IMemory> mIMemory; // ref-counted version, assigned only in constructor
int mFd; // file descriptor
int mIndent; // indentation level
audio_utils_fifo * const mFifo; // FIFO itself,
// non-NULL unless constructor fails
audio_utils_fifo_reader * const mFifoReader; // used to read from FIFO,
// non-NULL unless constructor fails
void dumpLine(const String8& timestamp, String8& body);
FormatEntry::iterator handleFormat(const FormatEntry &fmtEntry,
String8 *timestamp,
String8 *body);
// dummy method for handling absent author entry
virtual size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body) { return 0; }
// Searches for the last entry of type <type> in the range [front, back)
// back has to be entry-aligned. Returns nullptr if none enconuntered.
static uint8_t *findLastEntryOfType(uint8_t *front, uint8_t *back, uint8_t type);
static const size_t kSquashTimestamp = 5; // squash this many or more adjacent timestamps
};
// Wrapper for a reader with a name. Contains a pointer to the reader and a pointer to the name
class NamedReader {
public:
NamedReader() { mName[0] = '\0'; } // for Vector
NamedReader(const sp<NBLog::Reader>& reader, const char *name) :
mReader(reader)
{ strlcpy(mName, name, sizeof(mName)); }
~NamedReader() { }
const sp<NBLog::Reader>& reader() const { return mReader; }
const char* name() const { return mName; }
private:
sp<NBLog::Reader> mReader;
static const size_t kMaxName = 32;
char mName[kMaxName];
};
// ---------------------------------------------------------------------------
class Merger : public RefBase {
public:
Merger(const void *shared, size_t size);
virtual ~Merger() {}
void addReader(const NamedReader &reader);
// TODO add removeReader
void merge();
const std::vector<NamedReader> *getNamedReaders() const;
private:
// vector of the readers the merger is supposed to merge from.
// every reader reads from a writer's buffer
std::vector<NamedReader> mNamedReaders;
uint8_t *mBuffer;
Shared * const mShared;
std::unique_ptr<audio_utils_fifo> mFifo;
std::unique_ptr<audio_utils_fifo_writer> mFifoWriter;
static struct timespec getTimestamp(const uint8_t *data);
};
class MergeReader : public Reader {
public:
MergeReader(const void *shared, size_t size, Merger &merger);
private:
const std::vector<NamedReader> *mNamedReaders;
// handle author entry by looking up the author's name and appending it to the body
// returns number of bytes read from fmtEntry
size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body);
};
// MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
// when triggered, it awakes for a lapse of time, during which it periodically merges; if
// retriggered, the timeout is reset.
// The thread is triggered on AudioFlinger binder activity.
class MergeThread : public Thread {
public:
MergeThread(Merger &merger);
virtual ~MergeThread() override;
// Reset timeout and activate thread to merge periodically if it's idle
void wakeup();
// Set timeout period until the merging thread goes idle again
void setTimeoutUs(int time);
private:
virtual bool threadLoop() override;
// the merger who actually does the work of merging the logs
Merger& mMerger;
// mutex for the condition variable
Mutex mMutex;
// condition variable to activate merging on timeout >= 0
Condition mCond;
// time left until the thread blocks again (in microseconds)
int mTimeoutUs;
// merging period when the thread is awake
static const int kThreadSleepPeriodUs = 1000000 /*1s*/;
// initial timeout value when triggered
static const int kThreadWakeupPeriodUs = 3000000 /*3s*/;
};
}; // class NBLog
} // namespace android
#endif // ANDROID_MEDIA_NBLOG_H