Implement log merging.

Still missing:
 * Put in separate periodical thread

Bug: 35329293
Test: manual
Change-Id: Ie8802fb7972e20d8dec493376ea253bb782f3a46
diff --git a/include/media/nbaio/NBLog.h b/include/media/nbaio/NBLog.h
index 191f0cb..da80970 100644
--- a/include/media/nbaio/NBLog.h
+++ b/include/media/nbaio/NBLog.h
@@ -23,6 +23,8 @@
 #include <utils/Mutex.h>
 #include <audio_utils/fifo.h>
 
+#include <vector>
+
 namespace android {
 
 class String8;
@@ -40,9 +42,10 @@
     EVENT_RESERVED,
     EVENT_STRING,               // ASCII string, not NUL-terminated
     EVENT_TIMESTAMP,            // clock_gettime(CLOCK_MONOTONIC)
-    EVENT_INTEGER,
-    EVENT_FLOAT,
-    EVENT_PID,
+    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
@@ -68,6 +71,49 @@
     static const size_t kOverhead = 3;  // mEvent, mLength, mData[...], duplicate mLength
 };
 
+// ---------------------------------------------------------------------------
+// API for handling format entry operations
+
+// a formatted entry has the following structure:
+//    * START_FMT entry, containing the format string
+//    * author entry of the thread that generated it (optional, present in merged log)
+//    * TIMESTAMP entry
+//    * format arg1
+//    * format arg2
+//    * ...
+//    * END_FMT entry
+
+class FormatEntry {
+public:
+    // build a Format Entry starting in the given pointer
+    explicit FormatEntry(const uint8_t *entry);
+
+    // 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)
+    const uint8_t  *args() const;
+
+    // get format entry timestamp
+    timespec        timestamp() const;
+
+    // entry's author index (-1 if none present)
+    int             author() const;
+
+    // copy entry, adding author before timestamp, returns size of original entry
+    // (intended for merger)
+    size_t          copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const;
+private:
+    // copies ordinary entry from src to dst, and returns length of entry
+    size_t          copyEntry(std::unique_ptr<audio_utils_fifo_writer> &dst, const uint8_t *src)
+                        const;
+
+    const uint8_t  *mEntry;
+};
+
 // representation of a single log entry in shared memory
 //  byte[0]             mEvent
 //  byte[1]             mLength
@@ -82,9 +128,8 @@
     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 int     handleFormat(const char *fmt, size_t length, const uint8_t *data,
-                                String8 *timestamp, String8 *body);
     static void    appendTimestamp(String8 *body, const void *data);
+    static size_t  fmtEntryLength(const uint8_t *data);
 
 public:
 
@@ -215,6 +260,31 @@
 class Reader : public RefBase {
 public:
 
+    // A snapshot of a readers buffer
+    class Snapshot {
+    public:
+        Snapshot() : mData(NULL), mAvail(0), mLost(0) {}
+
+        Snapshot(size_t bufferSize) : mData(new uint8_t[bufferSize]) {}
+
+        ~Snapshot() { delete[] mData; }
+
+        // copy of the buffer
+        const uint8_t *data() const { return mData; }
+
+        // amount of data available (given by audio_utils_fifo_reader)
+        size_t   available() const { return mAvail; }
+
+        // amount of data lost (given by audio_utils_fifo_reader)
+        size_t   lost() const { return mLost; }
+
+    private:
+        friend class Reader;
+        const uint8_t *mData;
+        size_t         mAvail;
+        size_t         mLost;
+    };
+
     // 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);
@@ -222,8 +292,13 @@
 
     virtual ~Reader();
 
-    void    dump(int fd, size_t indent = 0);
-    bool    isIMemory(const sp<IMemory>& iMemory) const;
+    // 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
@@ -237,10 +312,66 @@
                                                     // non-NULL unless constructor fails
 
     void    dumpLine(const String8& timestamp, String8& body);
+    int     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; }
 
     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);
+};
+
 };  // class NBLog
 
 }   // namespace android
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk
index 7dc4e1d..63fa16b 100644
--- a/media/audioserver/Android.mk
+++ b/media/audioserver/Android.mk
@@ -12,6 +12,7 @@
 	libcutils \
 	liblog \
 	libmedialogservice \
+	libnbaio \
 	libradioservice \
 	libsoundtriggerservice \
 	libutils \
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index 03df94e..49caeb8 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -29,6 +29,8 @@
 #include <utils/Log.h>
 #include <utils/String8.h>
 
+#include <queue>
+
 namespace android {
 
 int NBLog::Entry::readAt(size_t offset) const
@@ -48,6 +50,75 @@
 
 // ---------------------------------------------------------------------------
 
+NBLog::FormatEntry::FormatEntry(const uint8_t *entry) : mEntry(entry) {
+    ALOGW_IF(entry[0] != EVENT_START_FMT,
+             "Created format entry with invalid event type %d",
+             entry[0]);
+}
+
+const char *NBLog::FormatEntry::formatString() const {
+    return (const char*) mEntry + 2;
+}
+
+size_t NBLog::FormatEntry::formatStringLength() const {
+    return mEntry[1];
+}
+
+const uint8_t *NBLog::FormatEntry::args() const {
+    const uint8_t *ptr = mEntry + mEntry[1] + NBLog::Entry::kOverhead;
+    if (ptr[0] != EVENT_TIMESTAMP) { // skip author if present
+        ptr += ptr[1] + NBLog::Entry::kOverhead;
+    }
+    return ptr + ptr[1] + NBLog::Entry::kOverhead;
+}
+
+timespec NBLog::FormatEntry::timestamp() const {
+    const uint8_t *ptr = mEntry + mEntry[1] + NBLog::Entry::kOverhead;
+    if (ptr[0] != EVENT_TIMESTAMP) { // skip authors if present
+        ptr += ptr[1] + NBLog::Entry::kOverhead;
+    }
+    // by this point, we should be standing in the timestamp entry
+    return *((struct timespec*) (&ptr[2]));
+}
+
+pid_t NBLog::FormatEntry::author() const {
+    size_t authorOffset = mEntry[1] + NBLog::Entry::kOverhead;
+    // return -1 if the entry has no author
+    if (mEntry[authorOffset] != EVENT_AUTHOR) {
+        return -1;
+    }
+    return *(pid_t*)(mEntry + authorOffset + 2);
+}
+
+size_t NBLog::FormatEntry::copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const {
+    // copy fmt start entry
+    size_t entryOffset = copyEntry(dst, mEntry);
+    // insert author entry
+    size_t authorEntrySize = NBLog::Entry::kOverhead + sizeof(author);
+    uint8_t authorEntry[authorEntrySize];
+    authorEntry[0] = EVENT_AUTHOR;
+    authorEntry[1] = authorEntry[authorEntrySize - 1] = sizeof(author);
+    *(int*) (&authorEntry[2]) = author;
+    dst->write(authorEntry, authorEntrySize);
+    // copy rest of entries
+    Event lastEvent = EVENT_TIMESTAMP;
+    while (lastEvent != EVENT_END_FMT) {
+        lastEvent = (Event) mEntry[entryOffset];
+        entryOffset += copyEntry(dst, mEntry + entryOffset);
+    }
+    return entryOffset;
+}
+
+
+size_t NBLog::FormatEntry::copyEntry(std::unique_ptr<audio_utils_fifo_writer> &dst,
+                                     const uint8_t *src) const {
+    size_t length = src[1] + NBLog::Entry::kOverhead;
+    dst->write(src, length);
+    return length;
+}
+
+// ---------------------------------------------------------------------------
+
 #if 0   // FIXME see note in NBLog.h
 NBLog::Timeline::Timeline(size_t size, void *shared)
     : mSize(roundup(size)), mOwn(shared == NULL),
@@ -463,43 +534,44 @@
     delete mFifo;
 }
 
-void NBLog::Reader::dump(int fd, size_t indent)
+std::unique_ptr<NBLog::Reader::Snapshot> NBLog::Reader::getSnapshot()
 {
     if (mFifoReader == NULL) {
-        return;
+        return std::unique_ptr<NBLog::Reader::Snapshot>(new Snapshot());
     }
     // make a copy to avoid race condition with writer
     size_t capacity = mFifo->capacity();
 
-    // TODO Stack-based allocation of large objects may fail.
-    //      Currently the log buffers are a page or two, which should be safe.
-    //      But if the log buffers ever get a lot larger,
-    //      then change this to allocate from heap when necessary.
-    static size_t kReasonableStackObjectSize = 32768;
-    ALOGW_IF(capacity > kReasonableStackObjectSize, "Stack-based allocation of object may fail");
-    uint8_t copy[capacity];
+    std::unique_ptr<Snapshot> snapshot(new Snapshot(capacity));
 
-    size_t lost;
-    ssize_t actual = mFifoReader->read(copy, capacity, NULL /*timeout*/, &lost);
+    ssize_t actual = mFifoReader->read((void*) snapshot->mData, capacity, NULL /*timeout*/,
+                                       &(snapshot->mLost));
     ALOG_ASSERT(actual <= capacity);
-    size_t avail = actual > 0 ? (size_t) actual : 0;
-    size_t i = avail;
+    snapshot->mAvail = actual > 0 ? (size_t) actual : 0;
+    return snapshot;
+}
+
+void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapshot)
+{
+    size_t i = snapshot.available();
+    const uint8_t *snapData = snapshot.data();
     Event event;
     size_t length;
     struct timespec ts;
     time_t maxSec = -1;
     while (i >= Entry::kOverhead) {
-        length = copy[i - 1];
-        if (length + Entry::kOverhead > i || copy[i - length - 2] != length) {
+        length = snapData[i - 1];
+        if (length + Entry::kOverhead > i || snapData[i - length - 2] != length) {
+
             break;
         }
-        event = (Event) copy[i - length - Entry::kOverhead];
+        event = (Event) snapData[i - length - Entry::kOverhead];
         if (event == EVENT_TIMESTAMP) {
             if (length != sizeof(struct timespec)) {
                 // corrupt
                 break;
             }
-            memcpy(&ts, &copy[i - length - 1], sizeof(struct timespec));
+            memcpy(&ts, &snapData[i - length - 1], sizeof(struct timespec));
             if (ts.tv_sec > maxSec) {
                 maxSec = ts.tv_sec;
             }
@@ -509,7 +581,7 @@
     mFd = fd;
     mIndent = indent;
     String8 timestamp, body;
-    lost += i;
+    size_t lost = snapshot.lost() + i;
     if (lost > 0) {
         body.appendFormat("warning: lost %zu bytes worth of events", lost);
         // TODO timestamp empty here, only other choice to wait for the first timestamp event in the
@@ -525,10 +597,10 @@
         timestamp.appendFormat("[%*s]", (int) width + 4, "");
     }
     bool deferredTimestamp = false;
-    while (i < avail) {
-        event = (Event) copy[i];
-        length = copy[i + 1];
-        const void *data = &copy[i + 2];
+    while (i < snapshot.available()) {
+        event = (Event) snapData[i];
+        length = snapData[i + 1];
+        const void *data = &snapData[i + 2];
         size_t advance = length + Entry::kOverhead;
         switch (event) {
         case EVENT_STRING:
@@ -544,11 +616,11 @@
             size_t j = i;
             for (;;) {
                 j += sizeof(struct timespec) + 3 /*Entry::kOverhead?*/;
-                if (j >= avail || (Event) copy[j] != EVENT_TIMESTAMP) {
+                if (j >= snapshot.available() || (Event) snapData[j] != EVENT_TIMESTAMP) {
                     break;
                 }
                 struct timespec tsNext;
-                memcpy(&tsNext, &copy[j + 2], sizeof(struct timespec));
+                memcpy(&tsNext, &snapData[j + 2], sizeof(struct timespec));
                 if (tsNext.tv_sec != ts.tv_sec) {
                     break;
                 }
@@ -594,8 +666,7 @@
             appendPID(&body, data, length);
             break;
         case EVENT_START_FMT:
-            advance += handleFormat((const char*) &copy[i + 2], length,
-                                    &copy[i + Entry::kOverhead + length], &timestamp, &body);
+            advance += handleFormat(FormatEntry(snapData + i), &timestamp, &body);
             break;
         case EVENT_END_FMT:
             body.appendFormat("warning: got to end format event");
@@ -617,6 +688,13 @@
     }
 }
 
+void NBLog::Reader::dump(int fd, size_t indent)
+{
+    // get a snapshot, dump it
+    std::unique_ptr<Snapshot> snap = getSnapshot();
+    dump(fd, indent, *snap);
+}
+
 void NBLog::Reader::dumpLine(const String8 &timestamp, String8 &body)
 {
     if (mFd >= 0) {
@@ -656,17 +734,23 @@
     body->appendFormat("<PID: %d, name: %.*s>", id, (int) (length - sizeof(pid_t)), name);
 }
 
-int NBLog::handleFormat(const char *fmt, size_t fmt_length, const uint8_t *data,
-                        String8 *timestamp, String8 *body) {
-    if (data[0] != EVENT_TIMESTAMP) {
-        ALOGW("NBLog Reader Expected timestamp event %d, got %d", EVENT_TIMESTAMP, data[0]);
-    }
-    struct timespec ts;
-    memcpy(&ts, &data[2], sizeof(ts));
+int NBLog::Reader::handleFormat(const FormatEntry &fmtEntry, String8 *timestamp, String8 *body) {
+    // log timestamp
+    struct timespec ts = fmtEntry.timestamp();
     timestamp->clear();
     timestamp->appendFormat("[%d.%03d]", (int) ts.tv_sec,
                     (int) (ts.tv_nsec / 1000000));
-    size_t data_offset = Entry::kOverhead + sizeof ts;
+    size_t fullLength = NBLog::Entry::kOverhead + sizeof(ts);
+
+    // log author (if present)
+    fullLength += handleAuthor(fmtEntry, body);
+
+    // log string
+    const uint8_t *args = fmtEntry.args();
+    size_t args_offset = 0;
+
+    const char* fmt = fmtEntry.formatString();
+    size_t fmt_length = fmtEntry.formatStringLength();
 
     for (size_t fmt_offset = 0; fmt_offset < fmt_length; ++fmt_offset) {
         if (fmt[fmt_offset] != '%') {
@@ -681,20 +765,20 @@
             continue;
         }
 
-        NBLog::Event event = (NBLog::Event) data[data_offset];
-        size_t length = data[data_offset + 1];
+        NBLog::Event event = (NBLog::Event) args[args_offset];
+        size_t length = args[args_offset + 1];
 
         // TODO check length for event type is correct
-        if(length != data[data_offset + length + 2]) {
+        if(length != args[args_offset + length + 2]) {
             ALOGW("NBLog Reader received different lengths %zu and %d for event %d", length,
-                  data[data_offset + length + 2], event);
+                  args[args_offset + length + 2], event);
             body->append("<invalid entry>");
             ++fmt_offset;
             continue;
         }
 
         // TODO: implement more complex formatting such as %.3f
-        void * datum = (void*) &data[data_offset + 2]; // pointer to the current event data
+        void * datum = (void*) &args[args_offset + 2]; // pointer to the current event args
         switch(fmt[fmt_offset])
         {
         case 's': // string
@@ -731,10 +815,98 @@
             ALOGW("NBLog Reader encountered unknown character %c", fmt[fmt_offset]);
         }
 
-        data_offset += length + Entry::kOverhead;
+        args_offset += length + Entry::kOverhead;
 
     }
-    return data_offset + Entry::kOverhead; // data offset + size of END_FMT event
+    fullLength += args_offset + Entry::kOverhead;
+    return fullLength;
+}
+
+// ---------------------------------------------------------------------------
+
+NBLog::Merger::Merger(const void *shared, size_t size):
+      mBuffer(NULL),
+      mShared((Shared *) shared),
+      mFifo(mShared != NULL ?
+        new audio_utils_fifo(size, sizeof(uint8_t),
+            mShared->mBuffer, mShared->mRear, NULL /*throttlesFront*/) : NULL),
+      mFifoWriter(mFifo != NULL ? new audio_utils_fifo_writer(*mFifo) : NULL)
+      {}
+
+void NBLog::Merger::addReader(const NBLog::NamedReader &reader) {
+    mNamedReaders.push_back(reader);
+}
+
+// items placed in priority queue during merge
+// composed by a timestamp and the index of the snapshot where the timestamp came from
+struct MergeItem
+{
+    struct timespec ts;
+    int index;
+    MergeItem(struct timespec ts, int index): ts(ts), index(index) {}
+};
+
+// operators needed for priority queue in merge
+bool operator>(const struct timespec &t1, const struct timespec &t2) {
+    return t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec);
+}
+
+bool operator>(const struct MergeItem &i1, const struct MergeItem &i2) {
+    return i1.ts > i2.ts ||
+        (i1.ts.tv_sec == i2.ts.tv_sec && i1.ts.tv_nsec == i2.ts.tv_nsec && i1.index > i2.index);
+}
+
+// Merge registered readers, sorted by timestamp
+void NBLog::Merger::merge() {
+    int nLogs = mNamedReaders.size();
+    std::vector<std::unique_ptr<NBLog::Reader::Snapshot>> snapshots(nLogs);
+    for (int i = 0; i < nLogs; ++i) {
+        snapshots[i] = mNamedReaders[i].reader()->getSnapshot();
+    }
+    // initialize offsets
+    std::vector<size_t> offsets(nLogs, 0);
+    // TODO custom heap implementation could allow to update top, improving performance
+    // for bursty buffers
+    std::priority_queue<MergeItem, std::vector<MergeItem>, std::greater<MergeItem>> timestamps;
+    for (int i = 0; i < nLogs; ++i)
+    {
+        if (snapshots[i]->available() > 0) {
+            timespec ts = FormatEntry(snapshots[i]->data()).timestamp();
+            MergeItem item(ts, i);
+            timestamps.push(item);
+        }
+    }
+
+    while (!timestamps.empty()) {
+        // find minimum timestamp
+        int index = timestamps.top().index;
+        // copy it to the log
+        size_t length = FormatEntry(snapshots[index]->data() + offsets[index]).copyTo(
+            mFifoWriter, index);
+        // update data structures
+        offsets[index] += length;
+        ALOGW_IF(offsets[index] > snapshots[index]->available(), "Overflown snapshot capacity");
+        timestamps.pop();
+        if (offsets[index] != snapshots[index]->available()) {
+            timespec ts = FormatEntry(snapshots[index]->data() + offsets[index]).timestamp();
+            MergeItem item(ts, index);
+            timestamps.emplace(item);
+        }
+    }
+}
+
+const std::vector<NBLog::NamedReader> *NBLog::Merger::getNamedReaders() const {
+    return &mNamedReaders;
+}
+
+NBLog::MergeReader::MergeReader(const void *shared, size_t size, Merger &merger)
+    : Reader(shared, size), mNamedReaders(merger.getNamedReaders()) {}
+
+size_t NBLog::MergeReader::handleAuthor(const NBLog::FormatEntry &fmtEntry, String8 *body) {
+    int author = fmtEntry.author();
+    const char* name = (*mNamedReaders)[author].name();
+    body->appendFormat("%s: ", name);
+    return NBLog::Entry::kOverhead + sizeof(author);
 }
 
 }   // namespace android
diff --git a/services/medialog/MediaLogService.cpp b/services/medialog/MediaLogService.cpp
index ab2f925..4c81436 100644
--- a/services/medialog/MediaLogService.cpp
+++ b/services/medialog/MediaLogService.cpp
@@ -26,7 +26,7 @@
 
 namespace android {
 
-static const char kDeadlockedString[] = "MediaLogService may be deadlocked\n";
+// static const char kDeadlockedString[] = "MediaLogService may be deadlocked\n";
 
 void MediaLogService::registerWriter(const sp<IMemory>& shared, size_t size, const char *name)
 {
@@ -36,9 +36,10 @@
         return;
     }
     sp<NBLog::Reader> reader(new NBLog::Reader(shared, size));
-    NamedReader namedReader(reader, name);
+    NBLog::NamedReader namedReader(reader, name);
     Mutex::Autolock _l(mLock);
     mNamedReaders.add(namedReader);
+    mMerger.addReader(namedReader);
 }
 
 void MediaLogService::unregisterWriter(const sp<IMemory>& shared)
@@ -81,7 +82,8 @@
         return NO_ERROR;
     }
 
-    Vector<NamedReader> namedReaders;
+#if 0
+    Vector<NBLog::NamedReader> namedReaders;
     {
         bool locked = dumpTryLock(mLock);
 
@@ -95,19 +97,23 @@
             }
             return NO_ERROR;
         }
-        namedReaders = mNamedReaders;
+            // namedReaders = mNamedReaders;
+            // for (size_t i = 0; i < namedReaders.size(); i++) {
+            //     const NBLog::NamedReader& namedReader = namedReaders[i];
+            //     if (fd >= 0) {
+            //         dprintf(fd, "\n%s:\n", namedReader.name());
+            //     } else {
+            //         ALOGI("%s:", namedReader.name());
+            //     }
+            //     namedReader.reader()->dump(fd, 0 /*indent*/);
+            // }
+
         mLock.unlock();
     }
+#endif
 
-    for (size_t i = 0; i < namedReaders.size(); i++) {
-        const NamedReader& namedReader = namedReaders[i];
-        if (fd >= 0) {
-            dprintf(fd, "\n%s:\n", namedReader.name());
-        } else {
-            ALOGI("%s:", namedReader.name());
-        }
-        namedReader.reader()->dump(fd, 0 /*indent*/);
-    }
+    mMerger.merge();
+    mMergeReader.dump(fd);
     return NO_ERROR;
 }
 
diff --git a/services/medialog/MediaLogService.h b/services/medialog/MediaLogService.h
index c9bf2eb..e934844 100644
--- a/services/medialog/MediaLogService.h
+++ b/services/medialog/MediaLogService.h
@@ -27,8 +27,13 @@
 {
     friend class BinderService<MediaLogService>;    // for MediaLogService()
 public:
-    MediaLogService() : BnMediaLogService() { }
-    virtual ~MediaLogService() { }
+    MediaLogService() :
+        BnMediaLogService(),
+        mMergerShared((NBLog::Shared*) malloc(NBLog::Timeline::sharedSize(kMergeBufferSize))),
+        mMerger(mMergerShared, kMergeBufferSize),
+        mMergeReader(mMergerShared, kMergeBufferSize, mMerger)
+    {ALOGI("Nico creating MergeReader");}
+    virtual ~MediaLogService() { free(mMergerShared); }
     virtual void onFirstRef() { }
 
     static const char*  getServiceName() { return "media.log"; }
@@ -47,23 +52,16 @@
     // Internal dump
     static const int kDumpLockRetries = 50;
     static const int kDumpLockSleepUs = 20000;
+    static const size_t kMergeBufferSize = 16 * 1024; // TODO determine good value for this
     static bool dumpTryLock(Mutex& mutex);
 
     Mutex               mLock;
-    class NamedReader {
-    public:
-        NamedReader() : mReader(0) { 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];
-    };
-    Vector<NamedReader> mNamedReaders;
+
+    Vector<NBLog::NamedReader> mNamedReaders;
+
+    NBLog::Shared *mMergerShared;
+    NBLog::Merger mMerger;
+    NBLog::MergeReader mMergeReader;
 };
 
 }   // namespace android