logd: liblog: logcat: Add Statistics

- logd add statistical collection and formatting
- liblog add android_logger_get_statistics call
- logcat add -S flag
- logcat add -b all

(cherry picked from commit b42f44b7c09697f4a699f167531742bbf9e00ac7)

Change-Id: I521753b1969ecd4590c956aeeb1557d101059d67
diff --git a/logd/Android.mk b/logd/Android.mk
index 744c9d3..3dd8e0f 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -13,12 +13,14 @@
     FlushCommand.cpp \
     LogBuffer.cpp \
     LogBufferElement.cpp \
-    LogTimes.cpp
+    LogTimes.cpp \
+    LogStatistics.cpp
 
 LOCAL_SHARED_LIBRARIES := \
     libsysutils \
     liblog \
-    libcutils
+    libcutils \
+    libutils
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 202d542..43a283c 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -38,6 +38,7 @@
     registerCmd(new ClearCmd(buf));
     registerCmd(new GetBufSizeCmd(buf));
     registerCmd(new GetBufSizeUsedCmd(buf));
+    registerCmd(new GetStatisticsCmd(buf));
 }
 
 CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
@@ -133,3 +134,41 @@
     cli->sendMsg(buf);
     return 0;
 }
+
+CommandListener::GetStatisticsCmd::GetStatisticsCmd(LogBuffer *buf)
+        : LogCommand("getStatistics")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    uid_t uid = cli->getUid();
+    gid_t gid = cli->getGid();
+    if (clientHasLogCredentials(cli)) {
+        uid = AID_ROOT;
+    }
+
+    unsigned int logMask = -1;
+    if (argc > 1) {
+        logMask = 0;
+        for (int i = 1; i < argc; ++i) {
+            int id = atoi(argv[i]);
+            if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+                cli->sendMsg("Range Error");
+                return 0;
+            }
+            logMask |= 1 << id;
+        }
+    }
+
+    char *buf = NULL;
+
+    mBuf.formatStatistics(&buf, uid, logMask);
+    if (!buf) {
+        cli->sendMsg("Failed");
+    } else {
+        cli->sendMsg(buf);
+        free(buf);
+    }
+    return 0;
+}
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index 861abbf..a841610 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -54,6 +54,7 @@
     LogBufferCmd(Clear)
     LogBufferCmd(GetBufSize)
     LogBufferCmd(GetBufSizeUsed)
+    LogBufferCmd(GetStatistics)
 };
 
 #endif
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index c5760f7..e16aa69 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -22,17 +22,13 @@
 #include <log/logger.h>
 
 #include "LogBuffer.h"
+#include "LogStatistics.h"
 #include "LogReader.h"
 
 #define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here?
 
 LogBuffer::LogBuffer(LastLogTimes *times)
         : mTimes(*times) {
-    int i;
-    for (i = 0; i < LOG_ID_MAX; i++) {
-        mSizes[i] = 0;
-        mElements[i] = 0;
-    }
     pthread_mutex_init(&mLogElementsLock, NULL);
 }
 
@@ -93,8 +89,7 @@
         LogTimeEntry::unlock();
     }
 
-    mSizes[log_id] += len;
-    mElements[log_id]++;
+    stats.add(len, log_id, uid, pid);
     maybePrune(log_id);
     pthread_mutex_unlock(&mLogElementsLock);
 }
@@ -104,10 +99,10 @@
 //
 // mLogElementsLock must be held when this function is called.
 void LogBuffer::maybePrune(log_id_t id) {
-    unsigned long sizes = mSizes[id];
+    size_t sizes = stats.sizes(id);
     if (sizes > LOG_BUFFER_SIZE) {
-        unsigned long sizeOver90Percent = sizes - ((LOG_BUFFER_SIZE * 9) / 10);
-        unsigned long elements = mElements[id];
+        size_t sizeOver90Percent = sizes - ((LOG_BUFFER_SIZE * 9) / 10);
+        size_t elements = stats.elements(id);
         unsigned long pruneRows = elements * sizeOver90Percent / sizes;
         elements /= 10;
         if (pruneRows <= elements) {
@@ -141,7 +136,7 @@
         LogBufferElement *e = *it;
         if (e->getLogId() == id) {
             if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
-                if (mSizes[id] > (2 * LOG_BUFFER_SIZE)) {
+                if (stats.sizes(id) > (2 * LOG_BUFFER_SIZE)) {
                     // kick a misbehaving log reader client off the island
                     oldest->release_Locked();
                 } else {
@@ -150,8 +145,7 @@
                 break;
             }
             it = mLogElements.erase(it);
-            mSizes[id] -= e->getMsgLen();
-            mElements[id]--;
+            stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
             delete e;
             pruneRows--;
         } else {
@@ -172,7 +166,7 @@
 // get the used space associated with "id".
 unsigned long LogBuffer::getSizeUsed(log_id_t id) {
     pthread_mutex_lock(&mLogElementsLock);
-    unsigned long retval = mSizes[id];
+    size_t retval = stats.sizes(id);
     pthread_mutex_unlock(&mLogElementsLock);
     return retval;
 }
@@ -221,3 +215,26 @@
 
     return max;
 }
+
+size_t LogBuffer::formatStatistics(char **strp, uid_t uid, unsigned int logMask) {
+    log_time oldest(CLOCK_MONOTONIC);
+
+    pthread_mutex_lock(&mLogElementsLock);
+
+    // Find oldest element in the log(s)
+    LogBufferElementCollection::iterator it;
+    for (it = mLogElements.begin(); it != mLogElements.end(); ++it) {
+        LogBufferElement *element = *it;
+
+        if ((logMask & (1 << element->getLogId()))) {
+            oldest = element->getMonotonicTime();
+            break;
+        }
+    }
+
+    size_t ret = stats.format(strp, uid, logMask, oldest);
+
+    pthread_mutex_unlock(&mLogElementsLock);
+
+    return ret;
+}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 1b50a8f..92dd107 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -25,6 +25,7 @@
 
 #include "LogBufferElement.h"
 #include "LogTimes.h"
+#include "LogStatistics.h"
 
 typedef android::List<LogBufferElement *> LogBufferElementCollection;
 
@@ -32,8 +33,7 @@
     LogBufferElementCollection mLogElements;
     pthread_mutex_t mLogElementsLock;
 
-    unsigned long mSizes[LOG_ID_MAX];
-    unsigned long mElements[LOG_ID_MAX];
+    LogStatistics stats;
 
 public:
     LastLogTimes &mTimes;
@@ -50,6 +50,8 @@
     void clear(log_id_t id);
     unsigned long getSize(log_id_t id);
     unsigned long getSizeUsed(log_id_t id);
+    // *strp uses malloc, use free to release.
+    size_t formatStatistics(char **strp, uid_t uid, unsigned int logMask);
 
 private:
     void maybePrune(log_id_t id);
@@ -57,4 +59,4 @@
 
 };
 
-#endif
+#endif // _LOGD_LOG_BUFFER_H__
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
new file mode 100644
index 0000000..50ce442
--- /dev/null
+++ b/logd/LogStatistics.cpp
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2014 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 <stdarg.h>
+#include <time.h>
+
+#include <log/logger.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String8.h>
+
+#include "LogStatistics.h"
+
+PidStatistics::PidStatistics(pid_t pid)
+        : pid(pid)
+        , mSizesTotal(0)
+        , mElementsTotal(0)
+        , mSizes(0)
+        , mElements(0) { }
+
+void PidStatistics::add(unsigned short size) {
+    mSizesTotal += size;
+    ++mElementsTotal;
+    mSizes += size;
+    ++mElements;
+}
+
+bool PidStatistics::subtract(unsigned short size) {
+    mSizes -= size;
+    --mElements;
+    return mElements == 0 && kill(pid, 0);
+}
+
+void PidStatistics::addTotal(size_t size, size_t element) {
+    if (pid == gone) {
+        mSizesTotal += size;
+        mElementsTotal += element;
+    }
+}
+
+UidStatistics::UidStatistics(uid_t uid)
+        : uid(uid) {
+    Pids.clear();
+}
+
+UidStatistics::~UidStatistics() {
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end();) {
+        delete (*it);
+        it = Pids.erase(it);
+    }
+}
+
+void UidStatistics::add(unsigned short size, pid_t pid) {
+    PidStatistics *p;
+    PidStatisticsCollection::iterator last;
+    PidStatisticsCollection::iterator it;
+    for (last = it = begin(); it != end(); last = it, ++it) {
+        p = *it;
+        if (pid == p->getPid()) {
+            p->add(size);
+            // poor-man sort, bubble upwards if bigger than last
+            if ((last != it) && ((*last)->sizesTotal() < p->sizesTotal())) {
+                Pids.erase(it);
+                Pids.insert(last, p);
+            }
+            return;
+        }
+    }
+    // poor-man sort, insert if bigger than last or last is the gone entry.
+    bool insert = (last != it)
+        && ((p->getPid() == p->gone)
+            || ((*last)->sizesTotal() < (size_t) size));
+    p = new PidStatistics(pid);
+    if (insert) {
+        Pids.insert(last, p);
+    } else {
+        Pids.push_back(p);
+    }
+    p->add(size);
+}
+
+void UidStatistics::subtract(unsigned short size, pid_t pid) {
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if (pid == p->getPid()) {
+            if (p->subtract(size)) {
+                size_t szsTotal = p->sizesTotal();
+                size_t elsTotal = p->elementsTotal();
+                delete p;
+                Pids.erase(it);
+                it = end();
+                --it;
+                if (it == end()) {
+                    p = new PidStatistics(p->gone);
+                    Pids.push_back(p);
+                } else {
+                    p = *it;
+                    if (p->getPid() != p->gone) {
+                        p = new PidStatistics(p->gone);
+                        Pids.push_back(p);
+                    }
+                }
+                p->addTotal(szsTotal, elsTotal);
+            }
+            return;
+        }
+    }
+}
+
+size_t UidStatistics::sizes(pid_t pid) {
+    size_t sizes = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            sizes += p->sizes();
+        }
+    }
+    return sizes;
+}
+
+size_t UidStatistics::elements(pid_t pid) {
+    size_t elements = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            elements += p->elements();
+        }
+    }
+    return elements;
+}
+
+size_t UidStatistics::sizesTotal(pid_t pid) {
+    size_t sizes = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            sizes += p->sizesTotal();
+        }
+    }
+    return sizes;
+}
+
+size_t UidStatistics::elementsTotal(pid_t pid) {
+    size_t elements = 0;
+    PidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        PidStatistics *p = *it;
+        if ((pid == pid_all) || (pid == p->getPid())) {
+            elements += p->elementsTotal();
+        }
+    }
+    return elements;
+}
+
+LidStatistics::LidStatistics() {
+    Uids.clear();
+}
+
+LidStatistics::~LidStatistics() {
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end();) {
+        delete (*it);
+        it = Uids.erase(it);
+    }
+}
+
+void LidStatistics::add(unsigned short size, uid_t uid, pid_t pid) {
+    UidStatistics *u;
+    UidStatisticsCollection::iterator it;
+    UidStatisticsCollection::iterator last;
+
+    if (uid == (uid_t) -1) { // init
+        uid = (uid_t) AID_ROOT;
+    }
+
+    for (last = it = begin(); it != end(); last = it, ++it) {
+        u = *it;
+        if (uid == u->getUid()) {
+            u->add(size, pid);
+            if ((last != it) && ((*last)->sizesTotal() < u->sizesTotal())) {
+                Uids.erase(it);
+                Uids.insert(last, u);
+            }
+            return;
+        }
+    }
+    u = new UidStatistics(uid);
+    if ((last != it) && ((*last)->sizesTotal() < (size_t) size)) {
+        Uids.insert(last, u);
+    } else {
+        Uids.push_back(u);
+    }
+    u->add(size, pid);
+}
+
+void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) {
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if (uid == u->getUid()) {
+            u->subtract(size, pid);
+            return;
+        }
+    }
+}
+
+size_t LidStatistics::sizes(uid_t uid, pid_t pid) {
+    size_t sizes = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            sizes += u->sizes(pid);
+        }
+    }
+    return sizes;
+}
+
+size_t LidStatistics::elements(uid_t uid, pid_t pid) {
+    size_t elements = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            elements += u->elements(pid);
+        }
+    }
+    return elements;
+}
+
+size_t LidStatistics::sizesTotal(uid_t uid, pid_t pid) {
+    size_t sizes = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            sizes += u->sizesTotal(pid);
+        }
+    }
+    return sizes;
+}
+
+size_t LidStatistics::elementsTotal(uid_t uid, pid_t pid) {
+    size_t elements = 0;
+    UidStatisticsCollection::iterator it;
+    for (it = begin(); it != end(); ++it) {
+        UidStatistics *u = *it;
+        if ((uid == uid_all) || (uid == u->getUid())) {
+            elements += u->elementsTotal(pid);
+        }
+    }
+    return elements;
+}
+
+LogStatistics::LogStatistics()
+        : start(CLOCK_MONOTONIC) {
+    log_id_for_each(i) {
+        mSizes[i] = 0;
+        mElements[i] = 0;
+    }
+}
+
+void LogStatistics::add(unsigned short size,
+                        log_id_t log_id, uid_t uid, pid_t pid) {
+    mSizes[log_id] += size;
+    ++mElements[log_id];
+    id(log_id).add(size, uid, pid);
+}
+
+void LogStatistics::subtract(unsigned short size,
+                             log_id_t log_id, uid_t uid, pid_t pid) {
+    mSizes[log_id] -= size;
+    --mElements[log_id];
+    id(log_id).subtract(size, uid, pid);
+}
+
+size_t LogStatistics::sizes(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).sizes(uid, pid);
+    }
+    size_t sizes = 0;
+    log_id_for_each(i) {
+        sizes += id(i).sizes(uid, pid);
+    }
+    return sizes;
+}
+
+size_t LogStatistics::elements(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).elements(uid, pid);
+    }
+    size_t elements = 0;
+    log_id_for_each(i) {
+        elements += id(i).elements(uid, pid);
+    }
+    return elements;
+}
+
+size_t LogStatistics::sizesTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).sizesTotal(uid, pid);
+    }
+    size_t sizes = 0;
+    log_id_for_each(i) {
+        sizes += id(i).sizesTotal(uid, pid);
+    }
+    return sizes;
+}
+
+size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) {
+    if (log_id != log_id_all) {
+        return id(log_id).elementsTotal(uid, pid);
+    }
+    size_t elements = 0;
+    log_id_for_each(i) {
+        elements += id(i).elementsTotal(uid, pid);
+    }
+    return elements;
+}
+
+size_t LogStatistics::format(char **buf,
+                             uid_t uid, unsigned int logMask, log_time oldest) {
+    const unsigned short spaces_current = 13;
+    const unsigned short spaces_total = 19;
+
+    if (*buf) {
+        free(buf);
+        *buf = NULL;
+    }
+
+    android::String8 string("        span -> size/num");
+    size_t oldLength;
+    short spaces = 2;
+
+    log_id_for_each(i) {
+        if (logMask & (1 << i)) {
+            oldLength = string.length();
+            string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i));
+            spaces += spaces_total + oldLength - string.length();
+        }
+    }
+
+    spaces = 1;
+    log_time t(CLOCK_MONOTONIC);
+    unsigned long long d = t.nsec() - start.nsec();
+    string.appendFormat("\nTotal%4llu:%02llu:%02llu.%09llu",
+                  d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+                  (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+        oldLength = string.length();
+        string.appendFormat("%*s%zu/%zu", spaces, "",
+                            sizesTotal(i), elementsTotal(i));
+        spaces += spaces_total + oldLength - string.length();
+    }
+
+    spaces = 1;
+    d = t.nsec() - oldest.nsec();
+    string.appendFormat("\nNow%6llu:%02llu:%02llu.%09llu",
+                  d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60,
+                  (d / NS_PER_SEC) % 60, d % NS_PER_SEC);
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+
+        size_t els = elements(i);
+        if (els) {
+            oldLength = string.length();
+            string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els);
+            spaces -= string.length() - oldLength;
+        }
+        spaces += spaces_total;
+    }
+
+    log_id_for_each(i) {
+        if (!(logMask & (1 << i))) {
+            continue;
+        }
+
+        bool header = false;
+        bool first = true;
+
+        UidStatisticsCollection::iterator ut;
+        for(ut = id(i).begin(); ut != id(i).end(); ++ut) {
+            UidStatistics *up = *ut;
+            if ((uid != AID_ROOT) && (uid != up->getUid())) {
+                continue;
+            }
+
+            PidStatisticsCollection::iterator pt = up->begin();
+            if (pt == up->end()) {
+                continue;
+            }
+
+            android::String8 intermediate;
+
+            if (!header) {
+                // header below tuned to match spaces_total and spaces_current
+                spaces = 0;
+                intermediate = string.format("%s: UID/PID Total size/num",
+                                             android_log_id_to_name(i));
+                string.appendFormat("\n\n%-31sNow          "
+                                         "UID/PID[?]  Total              Now",
+                                    intermediate.string());
+                intermediate.clear();
+                header = true;
+            }
+
+            bool oneline = ++pt == up->end();
+            --pt;
+
+            if (!oneline) {
+                first = true;
+            } else if (!first && spaces) {
+                string.appendFormat("%*s", spaces, "");
+            }
+            spaces = 0;
+
+            uid_t u = up->getUid();
+            pid_t p = (*pt)->getPid();
+
+            intermediate = string.format(oneline
+                                             ? ((p == PidStatistics::gone)
+                                                 ? "%d/?"
+                                                 : "%d/%d")
+                                             : "%d",
+                                         u, p);
+            string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                intermediate.string());
+            intermediate.clear();
+
+            size_t elsTotal = up->elementsTotal();
+            oldLength = string.length();
+            string.appendFormat("%zu/%zu", up->sizesTotal(), elsTotal);
+            spaces += spaces_total + oldLength - string.length();
+
+            size_t els = up->elements();
+            if (els == elsTotal) {
+                string.appendFormat("%*s=", spaces, "");
+                spaces = -1;
+            } else if (els) {
+                oldLength = string.length();
+                string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els);
+                spaces -= string.length() - oldLength;
+            }
+            spaces += spaces_current;
+
+            first = !first;
+
+            if (oneline) {
+                continue;
+            }
+
+            size_t gone_szs = 0;
+            size_t gone_els = 0;
+
+            for(; pt != up->end(); ++pt) {
+                PidStatistics *pp = *pt;
+                pid_t p = pp->getPid();
+
+                // If a PID no longer has any current logs, and is not
+                // active anymore, skip & report totals for gone.
+                elsTotal = pp->elementsTotal();
+                size_t szsTotal = pp->sizesTotal();
+                if (p == pp->gone) {
+                    gone_szs += szsTotal;
+                    gone_els += elsTotal;
+                    continue;
+                }
+                els = pp->elements();
+                bool gone = kill(p, 0);
+                if (gone && (els == 0)) {
+                    // ToDo: garbage collection: move this statistical bucket
+                    //       from its current UID/PID to UID/? (races and
+                    //       wrap around are our achilles heel). Below is
+                    //       merely lipservice to catch PIDs that were still
+                    //       around when the stats were pruned to zero.
+                    gone_szs += szsTotal;
+                    gone_els += elsTotal;
+                    continue;
+                }
+
+                if (!first && spaces) {
+                    string.appendFormat("%*s", spaces, "");
+                }
+                spaces = 0;
+
+                intermediate = string.format((gone) ? "%d/%d?" : "%d/%d", u, p);
+                string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                    intermediate.string());
+                intermediate.clear();
+
+                oldLength = string.length();
+                string.appendFormat("%zu/%zu", szsTotal, elsTotal);
+                spaces += spaces_total + oldLength - string.length();
+
+                if (els == elsTotal) {
+                    string.appendFormat("%*s=", spaces, "");
+                    spaces = -1;
+                } else if (els) {
+                    oldLength = string.length();
+                    string.appendFormat("%*s%zu/%zu", spaces, "",
+                                        pp->sizes(), els);
+                    spaces -= string.length() - oldLength;
+                }
+                spaces += spaces_current;
+
+                first = !first;
+            }
+
+            if (gone_els) {
+                if (!first && spaces) {
+                    string.appendFormat("%*s", spaces, "");
+                }
+
+                intermediate = string.format("%d/?", u);
+                string.appendFormat((first) ? "\n%-12s" : "%-12s",
+                                    intermediate.string());
+                intermediate.clear();
+
+                spaces = spaces_total + spaces_current;
+
+                oldLength = string.length();
+                string.appendFormat("%zu/%zu", gone_szs, gone_els);
+                spaces -= string.length() - oldLength;
+
+                first = !first;
+            }
+        }
+    }
+
+    // Calculate total buffer size prefix
+    char re_fmt[32];
+    size_t ret;
+    for(size_t l = string.length(), y = 0, x = 6;
+           y != x;
+           y = x, x = strlen(re_fmt) - 2) {
+       snprintf(re_fmt, sizeof(re_fmt), "%zu\n%%s\n\f", l + x);
+       ret = l + x;
+    }
+
+    android::String8 intermediate = string.format(re_fmt, string.string());
+    string.clear();
+
+    *buf = strdup(intermediate.string());
+
+    return ret;
+}
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
new file mode 100644
index 0000000..c8eeb45
--- /dev/null
+++ b/logd/LogStatistics.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 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 _LOGD_LOG_STATISTICS_H__
+#define _LOGD_LOG_STATISTICS_H__
+
+#include <sys/types.h>
+
+#include <log/log.h>
+#include <log/log_read.h>
+#include <utils/List.h>
+
+#define log_id_for_each(i) \
+    for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1))
+
+class PidStatistics {
+    const pid_t pid;
+
+    // Total
+    size_t mSizesTotal;
+    size_t mElementsTotal;
+    // Current
+    size_t mSizes;
+    size_t mElements;
+
+public:
+    static const pid_t gone = (pid_t) -1;
+
+    PidStatistics(pid_t pid);
+
+    pid_t getPid() const { return pid; }
+
+    void add(unsigned short size);
+    bool subtract(unsigned short size); // returns true if stats and PID gone
+    void addTotal(size_t size, size_t element);
+
+    size_t sizes() const { return mSizes; }
+    size_t elements() const { return mElements; }
+
+    size_t sizesTotal() const { return mSizesTotal; }
+    size_t elementsTotal() const { return mElementsTotal; }
+};
+
+typedef android::List<PidStatistics *> PidStatisticsCollection;
+
+class UidStatistics {
+    const uid_t uid;
+
+    PidStatisticsCollection Pids;
+
+public:
+    UidStatistics(uid_t uid);
+    ~UidStatistics();
+
+    PidStatisticsCollection::iterator begin() { return Pids.begin(); }
+    PidStatisticsCollection::iterator end() { return Pids.end(); }
+
+    uid_t getUid() { return uid; }
+
+    void add(unsigned short size, pid_t pid);
+    void subtract(unsigned short size, pid_t pid);
+
+    static const pid_t pid_all = (pid_t) -1;
+
+    size_t sizes(pid_t pid = pid_all);
+    size_t elements(pid_t pid = pid_all);
+
+    size_t sizesTotal(pid_t pid = pid_all);
+    size_t elementsTotal(pid_t pid = pid_all);
+};
+
+typedef android::List<UidStatistics *> UidStatisticsCollection;
+
+class LidStatistics {
+    UidStatisticsCollection Uids;
+
+public:
+    LidStatistics();
+    ~LidStatistics();
+
+    UidStatisticsCollection::iterator begin() { return Uids.begin(); }
+    UidStatisticsCollection::iterator end() { return Uids.end(); }
+
+    void add(unsigned short size, uid_t uid, pid_t pid);
+    void subtract(unsigned short size, uid_t uid, pid_t pid);
+
+    static const pid_t pid_all = (pid_t) -1;
+    static const uid_t uid_all = (uid_t) -1;
+
+    size_t sizes(uid_t uid = uid_all, pid_t pid = pid_all);
+    size_t elements(uid_t uid = uid_all, pid_t pid = pid_all);
+
+    size_t sizesTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+    size_t elementsTotal(uid_t uid = uid_all, pid_t pid = pid_all);
+};
+
+// Log Statistics
+class LogStatistics {
+    LidStatistics LogIds[LOG_ID_MAX];
+
+    size_t mSizes[LOG_ID_MAX];
+    size_t mElements[LOG_ID_MAX];
+
+public:
+    const log_time start;
+
+    LogStatistics();
+
+    LidStatistics &id(log_id_t log_id) { return LogIds[log_id]; }
+
+    void add(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+    void subtract(unsigned short size, log_id_t log_id, uid_t uid, pid_t pid);
+
+    // fast track current value by id only
+    size_t sizes(log_id_t id) const { return mSizes[id]; }
+    size_t elements(log_id_t id) const { return mElements[id]; }
+
+    // statistical track
+    static const log_id_t log_id_all = (log_id_t) -1;
+    static const uid_t uid_all = (uid_t) -1;
+    static const pid_t pid_all = (pid_t) -1;
+
+    size_t sizes(log_id_t id, uid_t uid, pid_t pid = pid_all);
+    size_t elements(log_id_t id, uid_t uid, pid_t pid = pid_all);
+    size_t sizes() { return sizes(log_id_all, uid_all); }
+    size_t elements() { return elements(log_id_all, uid_all); }
+
+    size_t sizesTotal(log_id_t id = log_id_all,
+                      uid_t uid = uid_all,
+                      pid_t pid = pid_all);
+    size_t elementsTotal(log_id_t id = log_id_all,
+                         uid_t uid = uid_all,
+                         pid_t pid = pid_all);
+
+    // *strp = malloc, balance with free
+    size_t format(char **strp, uid_t uid, unsigned int logMask, log_time oldest);
+};
+
+#endif // _LOGD_LOG_STATISTICS_H__