logd: Add minimum time bucket statistics

* Only enabled for dev builds because halves performance.
- Used to establish if /proc/sys/net/unix/max_dgram_qlen
  is appropriate for the platform
  (see logd/LogStatistics.cpp comments)
- enabled if logd.dgram_qlen.statistics is not zero/empty

Change-Id: Ib8ecfeb75b6f0f954c099a6b1032b8afb5efcbd4
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 1c5cef0..aa44264 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -37,11 +37,11 @@
 LogBuffer::LogBuffer(LastLogTimes *times)
         : mTimes(*times) {
     pthread_mutex_init(&mLogElementsLock, NULL);
-#ifdef USERDEBUG_BUILD
+    dgram_qlen_statistics = false;
+
     log_id_for_each(i) {
         mMaxSize[i] = LOG_BUFFER_SIZE;
     }
-#endif
 }
 
 void LogBuffer::log(log_id_t log_id, log_time realtime,
@@ -61,6 +61,23 @@
     LogBufferElementCollection::iterator last = it;
     while (--it != mLogElements.begin()) {
         if ((*it)->getRealTime() <= realtime) {
+            // halves the peak performance, use with caution
+            if (dgram_qlen_statistics) {
+                LogBufferElementCollection::iterator ib = it;
+                unsigned short buckets, num = 1;
+                for (unsigned short i = 0; (buckets = stats.dgram_qlen(i)); ++i) {
+                    buckets -= num;
+                    num += buckets;
+                    while (buckets && (--ib != mLogElements.begin())) {
+                        --buckets;
+                    }
+                    if (buckets) {
+                        break;
+                    }
+                    stats.recordDiff(
+                        elem->getRealTime() - (*ib)->getRealTime(), i);
+                }
+            }
             break;
         }
         last = it;
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index cbbb2ce..c3460ca 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -36,6 +36,8 @@
 
     LogStatistics stats;
 
+    bool dgram_qlen_statistics;
+
 #ifdef USERDEBUG_BUILD
     PruneList mPrune;
 
@@ -64,6 +66,11 @@
     // *strp uses malloc, use free to release.
     void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
 
+    void enableDgramQlenStatistics() {
+        stats.enableDgramQlenStatistics();
+        dgram_qlen_statistics = true;
+    }
+
 #ifdef USERDEBUG_BUILD
     int initPrune(char *cp) { return mPrune.init(cp); }
     // *strp uses malloc, use free to release.
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 49ee50d..5622ee5 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -275,6 +275,66 @@
         mSizes[i] = 0;
         mElements[i] = 0;
     }
+
+    dgram_qlen_statistics = false;
+    for(unsigned short bucket = 0; dgram_qlen(bucket); ++bucket) {
+        mMinimum[bucket].tv_sec = (uint32_t)-1;
+        mMinimum[bucket].tv_nsec = 999999999UL;
+    }
+}
+
+//   Each bucket below represents a dgram_qlen of log messages. By
+//   finding the minimum period of time from start to finish
+//   of each dgram_qlen, we can get a performance expectation for
+//   the user space logger. The net result is that the period
+//   of time divided by the dgram_qlen will give us the average time
+//   between log messages; at the point where the average time
+//   is greater than the throughput capability of the logger
+//   we will not longer require the benefits of the FIFO formed
+//   by max_dgram_qlen. We will also expect to see a very visible
+//   knee in the average time between log messages at this point,
+//   so we do not necessarily have to compare the rate against the
+//   measured performance (BM_log_maximum_retry) of the logger.
+//
+//   for example (reformatted):
+//
+//       Minimum time between log events per dgram_qlen:
+//       1   2   3   5   10  20  30  50  100  200 300 400 500 600
+//       5u2 12u 13u 15u 16u 27u 30u 36u 407u 3m1 3m3 3m9 3m9 5m5
+//
+//   demonstrates a clear knee rising at 100, so this means that for this
+//   case max_dgram_qlen = 100 would be more than sufficient to handle the
+//   worst that the system could stuff into the logger. The
+//   BM_log_maximum_retry performance (derated by the log collection) on the
+//   same system was 33.2us so we would almost be fine with max_dgram_qlen = 50.
+//   BM_log_maxumum_retry with statistics off is roughly 20us, so
+//   max_dgram_qlen = 20 would work. We will be more than willing to have
+//   a large engineering margin so the rule of thumb that lead us to 100 is
+//   fine.
+//
+// bucket dgram_qlen are tuned for /proc/sys/net/unix/max_dgram_qlen = 300
+const unsigned short LogStatistics::mBuckets[] = {
+    1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 400, 500, 600
+};
+
+unsigned short LogStatistics::dgram_qlen(unsigned short bucket) {
+    if (bucket >= sizeof(mBuckets) / sizeof(mBuckets[0])) {
+        return 0;
+    }
+    return mBuckets[bucket];
+}
+
+unsigned long long LogStatistics::minimum(unsigned short bucket) {
+    if (mMinimum[bucket].tv_sec == LONG_MAX) {
+        return 0;
+    }
+    return mMinimum[bucket].nsec();
+}
+
+void LogStatistics::recordDiff(log_time diff, unsigned short bucket) {
+    if ((diff.tv_sec || diff.tv_nsec) && (mMinimum[bucket] > diff)) {
+        mMinimum[bucket] = diff;
+    }
 }
 
 void LogStatistics::add(unsigned short size,
@@ -352,6 +412,9 @@
     log_id_for_each(i) {
         if (logMask & (1 << i)) {
             oldLength = string.length();
+            if (spaces < 0) {
+                spaces = 0;
+            }
             string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i));
             spaces += spaces_total + oldLength - string.length();
         }
@@ -369,6 +432,9 @@
             continue;
         }
         oldLength = string.length();
+        if (spaces < 0) {
+            spaces = 0;
+        }
         string.appendFormat("%*s%zu/%zu", spaces, "",
                             sizesTotal(i), elementsTotal(i));
         spaces += spaces_total + oldLength - string.length();
@@ -388,12 +454,64 @@
         size_t els = elements(i);
         if (els) {
             oldLength = string.length();
+            if (spaces < 0) {
+                spaces = 0;
+            }
             string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els);
             spaces -= string.length() - oldLength;
         }
         spaces += spaces_total;
     }
 
+    if (dgram_qlen_statistics) {
+        const unsigned short spaces_time = 6;
+        const unsigned long long max_seconds = 100000;
+        spaces = 0;
+        string.append("\n\nMinimum time between log events per dgram_qlen:\n");
+        for(unsigned short i = 0; dgram_qlen(i); ++i) {
+            oldLength = string.length();
+            if (spaces < 0) {
+                spaces = 0;
+            }
+            string.appendFormat("%*s%u", spaces, "", dgram_qlen(i));
+            spaces += spaces_time + oldLength - string.length();
+        }
+        string.append("\n");
+        spaces = 0;
+        unsigned short n;
+        for(unsigned short i = 0; (n = dgram_qlen(i)); ++i) {
+            unsigned long long duration = minimum(i);
+            if (duration) {
+                duration /= n;
+                if (duration >= (NS_PER_SEC * max_seconds)) {
+                    duration = NS_PER_SEC * (max_seconds - 1);
+                }
+                oldLength = string.length();
+                if (spaces < 0) {
+                    spaces = 0;
+                }
+                string.appendFormat("%*s", spaces, "");
+                if (duration >= (NS_PER_SEC * 10)) {
+                    string.appendFormat("%llu",
+                        (duration + (NS_PER_SEC / 2))
+                            / NS_PER_SEC);
+                } else if (duration >= (NS_PER_SEC / (1000 / 10))) {
+                    string.appendFormat("%llum",
+                        (duration + (NS_PER_SEC / 2 / 1000))
+                            / (NS_PER_SEC / 1000));
+                } else if (duration >= (NS_PER_SEC / (1000000 / 10))) {
+                    string.appendFormat("%lluu",
+                         (duration + (NS_PER_SEC / 2 / 1000000))
+                             / (NS_PER_SEC / 1000000));
+                } else {
+                    string.appendFormat("%llun", duration);
+                }
+                spaces -= string.length() - oldLength;
+            }
+            spaces += spaces_time;
+        }
+    }
+
     log_id_for_each(i) {
         if (!(logMask & (1 << i))) {
             continue;
@@ -433,7 +551,7 @@
 
             if (!oneline) {
                 first = true;
-            } else if (!first && spaces) {
+            } else if (!first && (spaces > 0)) {
                 string.appendFormat("%*s", spaces, "");
             }
             spaces = 0;
@@ -458,10 +576,16 @@
 
             size_t els = up->elements();
             if (els == elsTotal) {
+                if (spaces < 0) {
+                    spaces = 0;
+                }
                 string.appendFormat("%*s=", spaces, "");
                 spaces = -1;
             } else if (els) {
                 oldLength = string.length();
+                if (spaces < 0) {
+                    spaces = 0;
+                }
                 string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els);
                 spaces -= string.length() - oldLength;
             }
@@ -502,7 +626,7 @@
                     continue;
                 }
 
-                if (!first && spaces) {
+                if (!first && (spaces > 0)) {
                     string.appendFormat("%*s", spaces, "");
                 }
                 spaces = 0;
@@ -517,10 +641,16 @@
                 spaces += spaces_total + oldLength - string.length();
 
                 if (els == elsTotal) {
+                    if (spaces < 0) {
+                        spaces = 0;
+                    }
                     string.appendFormat("%*s=", spaces, "");
                     spaces = -1;
                 } else if (els) {
                     oldLength = string.length();
+                    if (spaces < 0) {
+                        spaces = 0;
+                    }
                     string.appendFormat("%*s%zu/%zu", spaces, "",
                                         pp->sizes(), els);
                     spaces -= string.length() - oldLength;
@@ -531,7 +661,7 @@
             }
 
             if (gone_els) {
-                if (!first && spaces) {
+                if (!first && (spaces > 0)) {
                     string.appendFormat("%*s", spaces, "");
                 }
 
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index d44afa2..6a26263 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -114,6 +114,11 @@
     size_t mSizes[LOG_ID_MAX];
     size_t mElements[LOG_ID_MAX];
 
+    bool dgram_qlen_statistics;
+
+    static const unsigned short mBuckets[14];
+    log_time mMinimum[sizeof(mBuckets) / sizeof(mBuckets[0])];
+
 public:
     const log_time start;
 
@@ -121,6 +126,11 @@
 
     LidStatistics &id(log_id_t log_id) { return LogIds[log_id]; }
 
+    void enableDgramQlenStatistics() { dgram_qlen_statistics = true; }
+    static unsigned short dgram_qlen(unsigned short bucket);
+    unsigned long long minimum(unsigned short bucket);
+    void recordDiff(log_time diff, unsigned short bucket);
+
     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);
 
diff --git a/logd/main.cpp b/logd/main.cpp
index 6216b95..8792d32 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -24,9 +24,12 @@
 #include <sys/capability.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <linux/prctl.h>
 
+#include <cutils/properties.h>
+
 #include "private/android_filesystem_config.h"
 #include "CommandListener.h"
 #include "LogBuffer.h"
@@ -92,6 +95,12 @@
 
     LogBuffer *logBuf = new LogBuffer(times);
 
+    char dgram_qlen_statistics[PROPERTY_VALUE_MAX];
+    property_get("logd.dgram_qlen.statistics", dgram_qlen_statistics, "");
+    if (atol(dgram_qlen_statistics)) {
+        logBuf->enableDgramQlenStatistics();
+    }
+
     // LogReader listens on /dev/socket/logdr. When a client
     // connects, log entries in the LogBuffer are written to the client.