Merge changes I1572338c,I39965007,I521753b1

* changes:
  logd: liblog: logcat: Add LogWhiteBlackList
  logd: prune by worst offending UID
  logd: liblog: logcat: Add Statistics
diff --git a/include/log/logger.h b/include/log/logger.h
index 8810615..8dab234 100644
--- a/include/log/logger.h
+++ b/include/log/logger.h
@@ -140,12 +140,24 @@
 log_id_t android_logger_get_id(struct logger *logger);
 
 int android_logger_clear(struct logger *logger);
-int android_logger_get_log_size(struct logger *logger);
-int android_logger_get_log_readable_size(struct logger *logger);
+long android_logger_get_log_size(struct logger *logger);
+#ifdef USERDEBUG_BUILD
+int android_logger_set_log_size(struct logger *logger, unsigned long size);
+#endif
+long android_logger_get_log_readable_size(struct logger *logger);
 int android_logger_get_log_version(struct logger *logger);
 
 struct logger_list;
 
+ssize_t android_logger_get_statistics(struct logger_list *logger_list,
+                                      char *buf, size_t len);
+#ifdef USERDEBUG_BUILD
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list,
+                                      char *buf, size_t len);
+int android_logger_set_prune_list(struct logger_list *logger_list,
+                                  char *buf, size_t len);
+#endif
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid);
diff --git a/liblog/Android.mk b/liblog/Android.mk
index c18dc48..4fe20db 100644
--- a/liblog/Android.mk
+++ b/liblog/Android.mk
@@ -22,6 +22,10 @@
 liblog_sources := logd_write_kern.c
 endif
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+liblog_cflags := -DUSERDEBUG_BUILD=1
+endif
+
 # some files must not be compiled when building against Mingw
 # they correspond to features not used by our host development tools
 # which are also hard or even impossible to port to native Win32
@@ -80,11 +84,13 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := liblog
 LOCAL_SRC_FILES := $(liblog_target_sources)
+LOCAL_CFLAGS := $(liblog_cflags)
 include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := liblog
 LOCAL_WHOLE_STATIC_LIBRARIES := liblog
+LOCAL_CFLAGS := $(liblog_cflags)
 include $(BUILD_SHARED_LIBRARY)
 
 include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/liblog/log_read.c b/liblog/log_read.c
index 889442d..e4acac2 100644
--- a/liblog/log_read.c
+++ b/liblog/log_read.c
@@ -296,11 +296,8 @@
     return ret;
 }
 
-int android_logger_clear(struct logger *logger)
+static int check_log_success(char *buf, ssize_t ret)
 {
-    char buf[512];
-
-    ssize_t ret = send_log_msg(logger, "clear %d", buf, sizeof(buf));
     if (ret < 0) {
         return ret;
     }
@@ -312,8 +309,16 @@
     return 0;
 }
 
+int android_logger_clear(struct logger *logger)
+{
+    char buf[512];
+
+    return check_log_success(buf,
+        send_log_msg(logger, "clear %d", buf, sizeof(buf)));
+}
+
 /* returns the total size of the log's ring buffer */
-int android_logger_get_log_size(struct logger *logger)
+long android_logger_get_log_size(struct logger *logger)
 {
     char buf[512];
 
@@ -326,14 +331,28 @@
         return -1;
     }
 
-    return atoi(buf);
+    return atol(buf);
 }
 
+#ifdef USERDEBUG_BUILD
+
+int android_logger_set_log_size(struct logger *logger, unsigned long size)
+{
+    char buf[512];
+
+    snprintf(buf, sizeof(buf), "setLogSize %d %lu",
+        logger ? logger->id : (unsigned) -1, size);
+
+    return check_log_success(buf, send_log_msg(NULL, NULL, buf, sizeof(buf)));
+}
+
+#endif /* USERDEBUG_BUILD */
+
 /*
  * returns the readable size of the log's ring buffer (that is, amount of the
  * log consumed)
  */
-int android_logger_get_log_readable_size(struct logger *logger)
+long android_logger_get_log_readable_size(struct logger *logger)
 {
     char buf[512];
 
@@ -346,7 +365,7 @@
         return -1;
     }
 
-    return atoi(buf);
+    return atol(buf);
 }
 
 /*
@@ -357,6 +376,58 @@
     return 3;
 }
 
+/*
+ * returns statistics
+ */
+ssize_t android_logger_get_statistics(struct logger_list *logger_list,
+                                      char *buf, size_t len)
+{
+    struct listnode *node;
+    struct logger *logger;
+    char *cp = buf;
+    size_t remaining = len;
+    size_t n;
+
+    n = snprintf(cp, remaining, "getStatistics");
+    n = min(n, remaining);
+    remaining -= n;
+    cp += n;
+
+    logger_for_each(logger, logger_list) {
+        n = snprintf(cp, remaining, " %d", logger->id);
+        n = min(n, remaining);
+        remaining -= n;
+        cp += n;
+    }
+    return send_log_msg(NULL, NULL, buf, len);
+}
+
+#ifdef USERDEBUG_BUILD
+
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    return send_log_msg(NULL, "getPruneList", buf, len);
+}
+
+int android_logger_set_prune_list(struct logger_list *logger_list UNUSED,
+                                  char *buf, size_t len)
+{
+    const char cmd[] = "setPruneList ";
+    const size_t cmdlen = sizeof(cmd) - 1;
+
+    if (strlen(buf) > (len - cmdlen)) {
+        return -ENOMEM; /* KISS */
+    }
+    memmove(buf + cmdlen, buf, len - cmdlen);
+    buf[len - 1] = '\0';
+    memcpy(buf, cmd, cmdlen);
+
+    return check_log_success(buf, send_log_msg(NULL, NULL, buf, len));
+}
+
+#endif /* USERDEBUG_BUILD */
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid)
diff --git a/liblog/log_read_kern.c b/liblog/log_read_kern.c
index b454729..483b6b6 100644
--- a/liblog/log_read_kern.c
+++ b/liblog/log_read_kern.c
@@ -51,6 +51,8 @@
          logger != node_to_item(&(logger_list)->node, struct logger, node); \
          logger = node_to_item((logger)->node.next, struct logger, node))
 
+#define UNUSED __attribute__((unused))
+
 /* In the future, we would like to make this list extensible */
 static const char *LOG_NAME[LOG_ID_MAX] = {
     [LOG_ID_MAIN] = "main",
@@ -225,16 +227,26 @@
 }
 
 /* returns the total size of the log's ring buffer */
-int android_logger_get_log_size(struct logger *logger)
+long android_logger_get_log_size(struct logger *logger)
 {
     return logger_ioctl(logger, LOGGER_GET_LOG_BUF_SIZE, O_RDWR);
 }
 
+#ifdef USERDEBUG_BUILD
+
+int android_logger_set_log_size(struct logger *logger UNUSED,
+                                unsigned long size UNUSED)
+{
+    return -ENOTSUP;
+}
+
+#endif /* USERDEBUG_BUILD */
+
 /*
  * returns the readable size of the log's ring buffer (that is, amount of the
  * log consumed)
  */
-int android_logger_get_log_readable_size(struct logger *logger)
+long android_logger_get_log_readable_size(struct logger *logger)
 {
     return logger_ioctl(logger, LOGGER_GET_LOG_LEN, O_RDONLY);
 }
@@ -248,6 +260,37 @@
     return (ret < 0) ? 1 : ret;
 }
 
+/*
+ * returns statistics
+ */
+static const char unsupported[] = "18\nNot Supported\n\f";
+
+ssize_t android_logger_get_statistics(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+#ifdef USERDEBUG_BUILD
+
+ssize_t android_logger_get_prune_list(struct logger_list *logger_list UNUSED,
+                                      char *buf, size_t len)
+{
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+int android_logger_set_prune_list(struct logger_list *logger_list UNUSED,
+                                  char *buf, size_t len)
+{
+    static const char unsupported_error[] = "Unsupported";
+    strncpy(buf, unsupported, len);
+    return -ENOTSUP;
+}
+
+#endif /* USERDEBUG_BUILD */
+
 struct logger_list *android_logger_list_alloc(int mode,
                                               unsigned int tail,
                                               pid_t pid)
diff --git a/logcat/Android.mk b/logcat/Android.mk
index b5e27eb..dd15cb3 100644
--- a/logcat/Android.mk
+++ b/logcat/Android.mk
@@ -3,6 +3,10 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+LOCAL_CFLAGS += -DUSERDEBUG_BUILD=1
+endif
+
 LOCAL_SRC_FILES:= logcat.cpp event.logtags
 
 LOCAL_SHARED_LIBRARIES := liblog
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 3c33938..00b5ba9 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -225,11 +225,23 @@
                     "  -t <count>      print only the most recent <count> lines (implies -d)\n"
                     "  -T <count>      print only the most recent <count> lines (does not imply -d)\n"
                     "  -g              get the size of the log's ring buffer and exit\n"
-                    "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio'\n"
-                    "                  or 'events'. Multiple -b parameters are allowed and the\n"
+                    "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio',\n"
+                    "                  'events' or 'all'. Multiple -b parameters are allowed and\n"
                     "                  results are interleaved. The default is -b main -b system.\n"
-                    "  -B              output the log in binary");
+                    "  -B              output the log in binary.\n"
+                    "  -S              output statistics.\n");
 
+#ifdef USERDEBUG_BUILD
+
+    fprintf(stderr, "--------------------- eng & userdebug builds only ---------------------------\n"
+                    "  -G <count>      set size of log's ring buffer and exit\n"
+                    "  -p              output prune white and ~black list\n"
+                    "  -P '<list> ...' set prune white and ~black list; UID, /PID or !(worst UID)\n"
+                    "                  default is ~!, prune worst UID.\n"
+                    "-----------------------------------------------------------------------------\n"
+    );
+
+#endif
 
     fprintf(stderr,"\nfilterspecs are a series of \n"
                    "  <tag>[:priority]\n\n"
@@ -278,6 +290,12 @@
     int hasSetLogFormat = 0;
     int clearLog = 0;
     int getLogSize = 0;
+#ifdef USERDEBUG_BUILD
+    unsigned long setLogSize = 0;
+    int getPruneList = 0;
+    char *setPruneList = NULL;
+#endif
+    int printStatistics = 0;
     int mode = O_RDONLY;
     const char *forceFilters = NULL;
     log_device_t* devices = NULL;
@@ -303,7 +321,13 @@
     for (;;) {
         int ret;
 
-        ret = getopt(argc, argv, "cdt:T:gsQf:r::n:v:b:B");
+        ret = getopt(argc, argv,
+#ifdef USERDEBUG_BUILD
+            "cdt:T:gG:sQf:r::n:v:b:BSpP:"
+#else
+            "cdt:T:gsQf:r::n:v:b:BS"
+#endif
+        );
 
         if (ret < 0) {
             break;
@@ -335,7 +359,89 @@
                 getLogSize = 1;
             break;
 
+#ifdef USERDEBUG_BUILD
+
+            case 'G': {
+                // would use atol if not for the multiplier
+                char *cp = optarg;
+                setLogSize = 0;
+                while (('0' <= *cp) && (*cp <= '9')) {
+                    setLogSize *= 10;
+                    setLogSize += *cp - '0';
+                    ++cp;
+                }
+
+                switch(*cp) {
+                case 'g':
+                case 'G':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case 'm':
+                case 'M':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case 'k':
+                case 'K':
+                    setLogSize *= 1024;
+                /* FALLTHRU */
+                case '\0':
+                break;
+
+                default:
+                    setLogSize = 0;
+                }
+
+                if (!setLogSize) {
+                    fprintf(stderr, "ERROR: -G <num><multiplier>\n");
+                    exit(1);
+                }
+            }
+            break;
+
+            case 'p':
+                getPruneList = 1;
+            break;
+
+            case 'P':
+                setPruneList = optarg;
+            break;
+
+#endif
+
             case 'b': {
+                if (strcmp(optarg, "all") == 0) {
+                    while (devices) {
+                        dev = devices;
+                        devices = dev->next;
+                        delete dev;
+                    }
+
+                    dev = devices = new log_device_t("main", false, 'm');
+                    android::g_devCount = 1;
+                    if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
+                        dev->next = new log_device_t("system", false, 's');
+                        if (dev->next) {
+                            dev = dev->next;
+                            android::g_devCount++;
+                        }
+                    }
+                    if (android_name_to_log_id("radio") == LOG_ID_RADIO) {
+                        dev->next = new log_device_t("radio", false, 'r');
+                        if (dev->next) {
+                            dev = dev->next;
+                            android::g_devCount++;
+                        }
+                    }
+                    if (android_name_to_log_id("events") == LOG_ID_EVENTS) {
+                        dev->next = new log_device_t("events", true, 'e');
+                        if (dev->next) {
+                            android::g_devCount++;
+                            needBinary = true;
+                        }
+                    }
+                    break;
+                }
+
                 bool binary = strcmp(optarg, "events") == 0;
                 if (binary) {
                     needBinary = true;
@@ -470,6 +576,10 @@
                 }
                 break;
 
+            case 'S':
+                printStatistics = 1;
+                break;
+
             default:
                 fprintf(stderr,"Unrecognized Option\n");
                 android::show_help(argv[0]);
@@ -563,8 +673,17 @@
             }
         }
 
+#ifdef USERDEBUG_BUILD
+
+        if (setLogSize && android_logger_set_log_size(dev->logger, setLogSize)) {
+            perror("setLogSize");
+            exit(EXIT_FAILURE);
+        }
+
+#endif
+
         if (getLogSize) {
-            int size, readable;
+            long size, readable;
 
             size = android_logger_get_log_size(dev->logger);
             if (size < 0) {
@@ -578,7 +697,7 @@
                 exit(EXIT_FAILURE);
             }
 
-            printf("%s: ring buffer is %dKb (%dKb consumed), "
+            printf("%s: ring buffer is %ldKb (%ldKb consumed), "
                    "max entry is %db, max payload is %db\n", dev->device,
                    size / 1024, readable / 1024,
                    (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD);
@@ -587,9 +706,95 @@
         dev = dev->next;
     }
 
+#ifdef USERDEBUG_BUILD
+
+    if (setPruneList) {
+        size_t len = strlen(setPruneList) + 32; // margin to allow rc
+        char *buf = (char *) malloc(len);
+
+        strcpy(buf, setPruneList);
+        int ret = android_logger_set_prune_list(logger_list, buf, len);
+        free(buf);
+
+        if (ret) {
+            perror("setPruneList");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+#endif
+
+    if (
+#ifdef USERDEBUG_BUILD
+        printStatistics || getPruneList
+#else
+        printStatistics
+#endif
+    ) {
+        size_t len = 8192;
+        char *buf;
+
+        for(int retry = 32;
+                (retry >= 0) && ((buf = new char [len]));
+                delete [] buf, --retry) {
+#ifdef USERDEBUG_BUILD
+            if (getPruneList) {
+                android_logger_get_prune_list(logger_list, buf, len);
+            } else {
+                android_logger_get_statistics(logger_list, buf, len);
+            }
+#else
+            android_logger_get_statistics(logger_list, buf, len);
+#endif
+            buf[len-1] = '\0';
+            size_t ret = atol(buf) + 1;
+            if (ret < 4) {
+                delete [] buf;
+                buf = NULL;
+                break;
+            }
+            bool check = ret <= len;
+            len = ret;
+            if (check) {
+                break;
+            }
+        }
+
+        if (!buf) {
+            perror("response read");
+            exit(EXIT_FAILURE);
+        }
+
+        // remove trailing FF
+        char *cp = buf + len - 1;
+        *cp = '\0';
+        bool truncated = *--cp != '\f';
+        if (!truncated) {
+            *cp = '\0';
+        }
+
+        // squash out the byte count
+        cp = buf;
+        if (!truncated) {
+            while (isdigit(*cp) || (*cp == '\n')) {
+                ++cp;
+            }
+        }
+
+        printf("%s", cp);
+        delete [] buf;
+        exit(0);
+    }
+
+
     if (getLogSize) {
         exit(0);
     }
+#ifdef USERDEBUG_BUILD
+    if (setLogSize || setPruneList) {
+        exit(0);
+    }
+#endif
     if (clearLog) {
         exit(0);
     }
diff --git a/logd/Android.mk b/logd/Android.mk
index 744c9d3..b0bc746 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -4,6 +4,10 @@
 
 LOCAL_MODULE:= logd
 
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+LOCAL_CFLAGS += -DUSERDEBUG_BUILD=1
+endif
+
 LOCAL_SRC_FILES := \
     main.cpp \
     LogCommand.cpp \
@@ -13,12 +17,15 @@
     FlushCommand.cpp \
     LogBuffer.cpp \
     LogBufferElement.cpp \
-    LogTimes.cpp
+    LogTimes.cpp \
+    LogStatistics.cpp \
+    LogWhiteBlackList.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..12b10ca 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -37,7 +37,15 @@
     // registerCmd(new ShutdownCmd(buf, writer, swl));
     registerCmd(new ClearCmd(buf));
     registerCmd(new GetBufSizeCmd(buf));
+#ifdef USERDEBUG_BUILD
+    registerCmd(new SetBufSizeCmd(buf));
+#endif
     registerCmd(new GetBufSizeUsedCmd(buf));
+    registerCmd(new GetStatisticsCmd(buf));
+#ifdef USERDEBUG_BUILD
+    registerCmd(new SetPruneListCmd(buf));
+    registerCmd(new GetPruneListCmd(buf));
+#endif
 }
 
 CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
@@ -109,6 +117,43 @@
     return 0;
 }
 
+#ifdef USERDEBUG_BUILD
+
+CommandListener::SetBufSizeCmd::SetBufSizeCmd(LogBuffer *buf)
+        : LogCommand("setLogSize")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::SetBufSizeCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    if (!clientHasLogCredentials(cli)) {
+        cli->sendMsg("Permission Denied");
+        return 0;
+    }
+
+    if (argc < 3) {
+        cli->sendMsg("Missing Argument");
+        return 0;
+    }
+
+    int id = atoi(argv[1]);
+    if ((id < LOG_ID_MIN) || (LOG_ID_MAX <= id)) {
+        cli->sendMsg("Range Error");
+        return 0;
+    }
+
+    unsigned long size = atol(argv[2]);
+    if (mBuf.setSize((log_id_t) id, size)) {
+        cli->sendMsg("Range Error");
+        return 0;
+    }
+
+    cli->sendMsg("success");
+    return 0;
+}
+
+#endif // USERDEBUG_BUILD
+
 CommandListener::GetBufSizeUsedCmd::GetBufSizeUsedCmd(LogBuffer *buf)
         : LogCommand("getLogSizeUsed")
         , mBuf(*buf)
@@ -133,3 +178,120 @@
     cli->sendMsg(buf);
     return 0;
 }
+
+CommandListener::GetStatisticsCmd::GetStatisticsCmd(LogBuffer *buf)
+        : LogCommand("getStatistics")
+        , mBuf(*buf)
+{ }
+
+static void package_string(char **strp) {
+    const char *a = *strp;
+    if (!a) {
+        a = "";
+    }
+
+    // Calculate total buffer size prefix, count is the string length w/o nul
+    char fmt[32];
+    for(size_t l = strlen(a), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) {
+        snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x);
+    }
+
+    char *b = *strp;
+    *strp = NULL;
+    asprintf(strp, fmt, a);
+    free(b);
+}
+
+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 {
+        package_string(&buf);
+        cli->sendMsg(buf);
+        free(buf);
+    }
+    return 0;
+}
+
+#ifdef USERDEBUG_BUILD
+
+CommandListener::GetPruneListCmd::GetPruneListCmd(LogBuffer *buf)
+        : LogCommand("getPruneList")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
+                                         int /*argc*/, char ** /*argv*/) {
+    char *buf = NULL;
+    mBuf.formatPrune(&buf);
+    if (!buf) {
+        cli->sendMsg("Failed");
+    } else {
+        package_string(&buf);
+        cli->sendMsg(buf);
+        free(buf);
+    }
+    return 0;
+}
+
+CommandListener::SetPruneListCmd::SetPruneListCmd(LogBuffer *buf)
+        : LogCommand("setPruneList")
+        , mBuf(*buf)
+{ }
+
+int CommandListener::SetPruneListCmd::runCommand(SocketClient *cli,
+                                         int argc, char **argv) {
+    if (!clientHasLogCredentials(cli)) {
+        cli->sendMsg("Permission Denied");
+        return 0;
+    }
+
+    char *cp = NULL;
+    for (int i = 1; i < argc; ++i) {
+        char *p = cp;
+        if (p) {
+            cp = NULL;
+            asprintf(&cp, "%s %s", p, argv[i]);
+            free(p);
+        } else {
+            asprintf(&cp, "%s", argv[i]);
+        }
+    }
+
+    int ret = mBuf.initPrune(cp);
+    free(cp);
+
+    if (ret) {
+        cli->sendMsg("Invalid");
+        return 0;
+    }
+
+    cli->sendMsg("success");
+
+    return 0;
+}
+
+#endif // USERDEBUG_BUILD
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index 861abbf..de1dcb9 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -53,7 +53,15 @@
 
     LogBufferCmd(Clear)
     LogBufferCmd(GetBufSize)
+#ifdef USERDEBUG_BUILD
+    LogBufferCmd(SetBufSize)
+#endif
     LogBufferCmd(GetBufSizeUsed)
+    LogBufferCmd(GetStatistics)
+#ifdef USERDEBUG_BUILD
+    LogBufferCmd(GetPruneList)
+    LogBufferCmd(SetPruneList)
+#endif
 };
 
 #endif
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index c5760f7..197b7e8 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -22,18 +22,26 @@
 #include <log/logger.h>
 
 #include "LogBuffer.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.h"
 #include "LogReader.h"
 
+// Default
 #define LOG_BUFFER_SIZE (256 * 1024) // Tuned on a per-platform basis here?
+#ifdef USERDEBUG_BUILD
+#define log_buffer_size(id) mMaxSize[id]
+#else
+#define log_buffer_size(id) LOG_BUFFER_SIZE
+#endif
 
 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);
+#ifdef USERDEBUG_BUILD
+    log_id_for_each(i) {
+        mMaxSize[i] = LOG_BUFFER_SIZE;
+    }
+#endif
 }
 
 void LogBuffer::log(log_id_t log_id, log_time realtime,
@@ -93,8 +101,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 +111,10 @@
 //
 // mLogElementsLock must be held when this function is called.
 void LogBuffer::maybePrune(log_id_t id) {
-    unsigned long sizes = mSizes[id];
-    if (sizes > LOG_BUFFER_SIZE) {
-        unsigned long sizeOver90Percent = sizes - ((LOG_BUFFER_SIZE * 9) / 10);
-        unsigned long elements = mElements[id];
+    size_t sizes = stats.sizes(id);
+    if (sizes > log_buffer_size(id)) {
+        size_t sizeOver90Percent = sizes - ((log_buffer_size(id) * 9) / 10);
+        size_t elements = stats.elements(id);
         unsigned long pruneRows = elements * sizeOver90Percent / sizes;
         elements /= 10;
         if (pruneRows <= elements) {
@@ -136,22 +143,117 @@
         t++;
     }
 
-    LogBufferElementCollection::iterator it = mLogElements.begin();
+    LogBufferElementCollection::iterator it;
+
+    // prune by worst offender by uid
+    while (pruneRows > 0) {
+        // recalculate the worst offender on every batched pass
+        uid_t worst = (uid_t) -1;
+        size_t worst_sizes = 0;
+        size_t second_worst_sizes = 0;
+
+#ifdef USERDEBUG_BUILD
+        if (mPrune.worstUidEnabled())
+#endif
+        {
+            LidStatistics &l = stats.id(id);
+            UidStatisticsCollection::iterator iu;
+            for (iu = l.begin(); iu != l.end(); ++iu) {
+                UidStatistics *u = (*iu);
+                size_t sizes = u->sizes();
+                if (worst_sizes < sizes) {
+                    second_worst_sizes = worst_sizes;
+                    worst_sizes = sizes;
+                    worst = u->getUid();
+                }
+                if ((second_worst_sizes < sizes) && (sizes < worst_sizes)) {
+                    second_worst_sizes = sizes;
+                }
+            }
+        }
+
+        bool kick = false;
+        for(it = mLogElements.begin(); it != mLogElements.end();) {
+            LogBufferElement *e = *it;
+
+            if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+                break;
+            }
+
+            if (e->getLogId() != id) {
+                ++it;
+                continue;
+            }
+
+            uid_t uid = e->getUid();
+
+            if (uid == worst) {
+                it = mLogElements.erase(it);
+                unsigned short len = e->getMsgLen();
+                stats.subtract(len, id, worst, e->getPid());
+                delete e;
+                kick = true;
+                pruneRows--;
+                if ((pruneRows == 0) || (worst_sizes < second_worst_sizes)) {
+                    break;
+                }
+                worst_sizes -= len;
+            }
+#ifdef USERDEBUG_BUILD
+            else if (mPrune.naughty(e)) { // BlackListed
+                it = mLogElements.erase(it);
+                stats.subtract(e->getMsgLen(), id, uid, e->getPid());
+                delete e;
+                pruneRows--;
+                if (pruneRows == 0) {
+                    break;
+                }
+            }
+#endif
+            else {
+                ++it;
+            }
+        }
+
+        if (!kick
+#ifdef USERDEBUG_BUILD
+                || !mPrune.worstUidEnabled()
+#endif
+        ) {
+            break; // the following loop will ask bad clients to skip/drop
+        }
+    }
+
+#ifdef USERDEBUG_BUILD
+    bool whitelist = false;
+#endif
+    it = mLogElements.begin();
     while((pruneRows > 0) && (it != mLogElements.end())) {
         LogBufferElement *e = *it;
         if (e->getLogId() == id) {
             if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
-                if (mSizes[id] > (2 * LOG_BUFFER_SIZE)) {
-                    // kick a misbehaving log reader client off the island
-                    oldest->release_Locked();
-                } else {
-                    oldest->triggerSkip_Locked(pruneRows);
+#ifdef USERDEBUG_BUILD
+                if (!whitelist)
+#endif
+                {
+                    if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+                        // kick a misbehaving log reader client off the island
+                        oldest->release_Locked();
+                    } else {
+                        oldest->triggerSkip_Locked(pruneRows);
+                    }
                 }
                 break;
             }
+#ifdef USERDEBUG_BUILD
+            if (mPrune.nice(e)) { // WhiteListed
+                whitelist = true;
+                it++;
+                continue;
+            }
+#endif
             it = mLogElements.erase(it);
-            mSizes[id] -= e->getMsgLen();
-            mElements[id]--;
+            stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
             delete e;
             pruneRows--;
         } else {
@@ -159,6 +261,32 @@
         }
     }
 
+#ifdef USERDEBUG_BUILD
+    if (whitelist && (pruneRows > 0)) {
+        it = mLogElements.begin();
+        while((it != mLogElements.end()) && (pruneRows > 0)) {
+            LogBufferElement *e = *it;
+            if (e->getLogId() == id) {
+                if (oldest && (oldest->mStart <= e->getMonotonicTime())) {
+                    if (stats.sizes(id) > (2 * log_buffer_size(id))) {
+                        // kick a misbehaving log reader client off the island
+                        oldest->release_Locked();
+                    } else {
+                        oldest->triggerSkip_Locked(pruneRows);
+                    }
+                    break;
+                }
+                it = mLogElements.erase(it);
+                stats.subtract(e->getMsgLen(), id, e->getUid(), e->getPid());
+                delete e;
+                pruneRows--;
+            } else {
+                it++;
+            }
+        }
+    }
+#endif
+
     LogTimeEntry::unlock();
 }
 
@@ -172,16 +300,42 @@
 // 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;
 }
 
+#ifdef USERDEBUG_BUILD
+
+// set the total space allocated to "id"
+int LogBuffer::setSize(log_id_t id, unsigned long size) {
+    // Reasonable limits ...
+    if ((size < (64 * 1024)) || ((256 * 1024 * 1024) < size)) {
+        return -1;
+    }
+    pthread_mutex_lock(&mLogElementsLock);
+    log_buffer_size(id) = size;
+    pthread_mutex_unlock(&mLogElementsLock);
+    return 0;
+}
+
+// get the total space allocated to "id"
+unsigned long LogBuffer::getSize(log_id_t id) {
+    pthread_mutex_lock(&mLogElementsLock);
+    size_t retval = log_buffer_size(id);
+    pthread_mutex_unlock(&mLogElementsLock);
+    return retval;
+}
+
+#else // ! USERDEBUG_BUILD
+
 // get the total space allocated to "id"
 unsigned long LogBuffer::getSize(log_id_t /*id*/) {
-    return LOG_BUFFER_SIZE;
+    return log_buffer_size(id);
 }
 
+#endif
+
 log_time LogBuffer::flushTo(
         SocketClient *reader, const log_time start, bool privileged,
         bool (*filter)(const LogBufferElement *element, void *arg), void *arg) {
@@ -221,3 +375,24 @@
 
     return max;
 }
+
+void 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;
+        }
+    }
+
+    stats.format(strp, uid, logMask, oldest);
+
+    pthread_mutex_unlock(&mLogElementsLock);
+}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 1b50a8f..0745e56 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -25,6 +25,8 @@
 
 #include "LogBufferElement.h"
 #include "LogTimes.h"
+#include "LogStatistics.h"
+#include "LogWhiteBlackList.h"
 
 typedef android::List<LogBufferElement *> LogBufferElementCollection;
 
@@ -32,8 +34,13 @@
     LogBufferElementCollection mLogElements;
     pthread_mutex_t mLogElementsLock;
 
-    unsigned long mSizes[LOG_ID_MAX];
-    unsigned long mElements[LOG_ID_MAX];
+    LogStatistics stats;
+
+#ifdef USERDEBUG_BUILD
+    PruneList mPrune;
+
+    unsigned long mMaxSize[LOG_ID_MAX];
+#endif
 
 public:
     LastLogTimes &mTimes;
@@ -49,7 +56,18 @@
 
     void clear(log_id_t id);
     unsigned long getSize(log_id_t id);
+#ifdef USERDEBUG_BUILD
+    int setSize(log_id_t id, unsigned long size);
+#endif
     unsigned long getSizeUsed(log_id_t id);
+    // *strp uses malloc, use free to release.
+    void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
+
+#ifdef USERDEBUG_BUILD
+    int initPrune(char *cp) { return mPrune.init(cp); }
+    // *strp uses malloc, use free to release.
+    void formatPrune(char **strp) { mPrune.format(strp); }
+#endif
 
 private:
     void maybePrune(log_id_t id);
@@ -57,4 +75,4 @@
 
 };
 
-#endif
+#endif // _LOGD_LOG_BUFFER_H__
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
new file mode 100644
index 0000000..49ee50d
--- /dev/null
+++ b/logd/LogStatistics.cpp
@@ -0,0 +1,555 @@
+/*
+ * 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;
+}
+
+void 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;
+            }
+        }
+    }
+
+    *buf = strdup(string.string());
+}
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
new file mode 100644
index 0000000..d44afa2
--- /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
+    void format(char **strp, uid_t uid, unsigned int logMask, log_time oldest);
+};
+
+#endif // _LOGD_LOG_STATISTICS_H__
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
new file mode 100644
index 0000000..d0ceb9f
--- /dev/null
+++ b/logd/LogWhiteBlackList.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+#ifdef USERDEBUG_BUILD
+
+#include <ctype.h>
+
+#include <utils/String8.h>
+
+#include "LogWhiteBlackList.h"
+
+// White and Black list
+
+Prune::Prune(uid_t uid, pid_t pid)
+        : mUid(uid)
+        , mPid(pid)
+{ }
+
+int Prune::cmp(uid_t uid, pid_t pid) const {
+    if ((mUid == uid_all) || (mUid == uid)) {
+        if (mPid == pid_all) {
+            return 0;
+        }
+        return pid - mPid;
+    }
+    return uid - mUid;
+}
+
+void Prune::format(char **strp) {
+    if (mUid != uid_all) {
+        asprintf(strp, (mPid != pid_all) ? "%u/%u" : "%u", mUid, mPid);
+    } else {
+        // NB: mPid == pid_all can not happen if mUid == uid_all
+        asprintf(strp, (mPid != pid_all) ? "/%u" : "/", mPid);
+    }
+}
+
+PruneList::PruneList()
+        : mWorstUidEnabled(true) {
+    mNaughty.clear();
+    mNice.clear();
+}
+
+PruneList::~PruneList() {
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end();) {
+        delete (*it);
+        it = mNice.erase(it);
+    }
+    for (it = mNaughty.begin(); it != mNaughty.end();) {
+        delete (*it);
+        it = mNaughty.erase(it);
+    }
+}
+
+int PruneList::init(char *str) {
+    mWorstUidEnabled = true;
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end();) {
+        delete (*it);
+        it = mNice.erase(it);
+    }
+    for (it = mNaughty.begin(); it != mNaughty.end();) {
+        delete (*it);
+        it = mNaughty.erase(it);
+    }
+
+    if (!str) {
+        return 0;
+    }
+
+    mWorstUidEnabled = false;
+
+    for(; *str; ++str) {
+        if (isspace(*str)) {
+            continue;
+        }
+
+        PruneCollection *list;
+        if ((*str == '~') || (*str == '!')) { // ~ supported, ! undocumented
+            ++str;
+            // special case, translates to worst UID at priority in blacklist
+            if (*str == '!') {
+                mWorstUidEnabled = true;
+                ++str;
+                if (!*str) {
+                    break;
+                }
+                if (!isspace(*str)) {
+                    return 1;
+                }
+                continue;
+            }
+            if (!*str) {
+                return 1;
+            }
+            list = &mNaughty;
+        } else {
+            list = &mNice;
+        }
+
+        uid_t uid = Prune::uid_all;
+        if (isdigit(*str)) {
+            uid = 0;
+            do {
+                uid = uid * 10 + *str++ - '0';
+            } while (isdigit(*str));
+        }
+
+        pid_t pid = Prune::pid_all;
+        if (*str == '/') {
+            ++str;
+            if (isdigit(*str)) {
+                pid = 0;
+                do {
+                    pid = pid * 10 + *str++ - '0';
+                } while (isdigit(*str));
+            }
+        }
+
+        if ((uid == Prune::uid_all) && (pid == Prune::pid_all)) {
+            return 1;
+        }
+
+        if (*str && !isspace(*str)) {
+            return 1;
+        }
+
+        // insert sequentially into list
+        PruneCollection::iterator it = list->begin();
+        while (it != list->end()) {
+            Prune *p = *it;
+            int m = uid - p->mUid;
+            if (m == 0) {
+                if (p->mPid == p->pid_all) {
+                    break;
+                }
+                if ((pid == p->pid_all) && (p->mPid != p->pid_all)) {
+                    it = list->erase(it);
+                    continue;
+                }
+                m = pid - p->mPid;
+            }
+            if (m >= 0) {
+                if (m > 0) {
+                    list->insert(it, new Prune(uid,pid));
+                }
+                break;
+            }
+            ++it;
+        }
+        if (it == list->end()) {
+            list->push_back(new Prune(uid,pid));
+        }
+        if (!*str) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+void PruneList::format(char **strp) {
+    if (*strp) {
+        free(*strp);
+        *strp = NULL;
+    }
+
+    static const char nice_format[] = " %s";
+    const char *fmt = nice_format + 1;
+
+    android::String8 string;
+
+    if (mWorstUidEnabled) {
+        string.setTo("~!");
+        fmt = nice_format;
+    }
+
+    PruneCollection::iterator it;
+
+    for (it = mNice.begin(); it != mNice.end(); ++it) {
+        char *a = NULL;
+        (*it)->format(&a);
+
+        string.appendFormat(fmt, a);
+        fmt = nice_format;
+
+        free(a);
+    }
+
+    static const char naughty_format[] = " ~%s";
+    fmt = naughty_format + (*fmt != ' ');
+    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
+        char *a = NULL;
+        (*it)->format(&a);
+
+        string.appendFormat(fmt, a);
+        fmt = naughty_format;
+
+        free(a);
+    }
+
+    *strp = strdup(string.string());
+}
+
+// ToDo: Lists are in sorted order, Prune->cmp() returns + or -
+// If there is scaling issues, resort to a better algorithm than linear
+// based on these assumptions.
+
+bool PruneList::naughty(LogBufferElement *element) {
+    PruneCollection::iterator it;
+    for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
+        if (!(*it)->cmp(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool PruneList::nice(LogBufferElement *element) {
+    PruneCollection::iterator it;
+    for (it = mNice.begin(); it != mNice.end(); ++it) {
+        if (!(*it)->cmp(element)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+#endif // USERDEBUG_BUILD
diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h
new file mode 100644
index 0000000..769d651
--- /dev/null
+++ b/logd/LogWhiteBlackList.h
@@ -0,0 +1,71 @@
+/*
+ * 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_WHITE_BLACK_LIST_H__
+#define _LOGD_LOG_WHITE_BLACK_LIST_H__
+
+#include <sys/types.h>
+
+#include <utils/List.h>
+
+#include <LogBufferElement.h>
+
+// White and Blacklist
+
+class Prune {
+    friend class PruneList;
+
+    const uid_t mUid;
+    const pid_t mPid;
+    int cmp(uid_t uid, pid_t pid) const;
+
+public:
+    static const uid_t uid_all = (uid_t) -1;
+    static const pid_t pid_all = (pid_t) -1;
+
+    Prune(uid_t uid, pid_t pid);
+
+    uid_t getUid() const { return mUid; }
+    pid_t getPid() const { return mPid; }
+
+    int cmp(LogBufferElement *e) const { return cmp(e->getUid(), e->getPid()); }
+
+    // *strp is malloc'd, use free to release
+    void format(char **strp);
+};
+
+typedef android::List<Prune *> PruneCollection;
+
+class PruneList {
+    PruneCollection mNaughty;
+    PruneCollection mNice;
+    bool mWorstUidEnabled;
+
+public:
+    PruneList();
+    ~PruneList();
+
+    int init(char *str);
+
+    bool naughty(LogBufferElement *element);
+    bool nice(LogBufferElement *element);
+    bool worstUidEnabled() const { return mWorstUidEnabled; }
+
+    // *strp is malloc'd, use free to release
+    void format(char **strp);
+};
+
+#endif // _LOGD_LOG_WHITE_BLACK_LIST_H__