metrics: Add an option to metrics_client to dump the logs cache.

The format of the metrics dump is rudimentary just yet. Here is an example:

  Metrics from /data/misc/metrics/uma-events

  name: hello     type: USER_ACTION
  name: world     type: USER_ACTION

This required the following changes:
* Added -d option to metrics_client
* Refactored file handling in SerializationUtils
 - Factored out file opening and log parsing into helper methods
 - Added ReadMetricsFromFile which is read-only so does not truncate the file

Change-Id: I6032d74242c79c678ec42a14e78fccc54e7af455
diff --git a/metricsd/metrics_client.cc b/metricsd/metrics_client.cc
index 57e96c2..f658b22 100644
--- a/metricsd/metrics_client.cc
+++ b/metricsd/metrics_client.cc
@@ -17,9 +17,15 @@
 #include <cstdio>
 #include <cstdlib>
 
+#include <base/memory/scoped_vector.h>
+
+#include "constants.h"
 #include "metrics/metrics_library.h"
+#include "serialization/metric_sample.h"
+#include "serialization/serialization_utils.h"
 
 enum Mode {
+    kModeDumpLogs,
     kModeSendSample,
     kModeSendEnumSample,
     kModeSendSparseSample,
@@ -36,12 +42,13 @@
           "        metrics_client -s   name sample\n"
           "        metrics_client -v   event\n"
           "        metrics_client -u action\n"
-          "        metrics_client [-cg]\n"
+          "        metrics_client [-cdg]\n"
           "\n"
           "  default: send metric with integer values \n"
           "           |min| > 0, |min| <= sample < |max|\n"
           "  -c: return exit status 0 if user consents to stats, 1 otherwise,\n"
           "      in guest mode always return 1\n"
+          "  -d: dump cached logs to the console\n"
           "  -e: send linear/enumeration histogram data\n"
           "  -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
           "  -s: send a sparse histogram sample\n"
@@ -132,17 +139,57 @@
   return metrics_lib.IsGuestMode() ? 0 : 1;
 }
 
+static int DumpLogs() {
+  printf("Metrics from %s\n\n", metrics::kMetricsEventsFilePath);
+
+  ScopedVector<metrics::MetricSample> metrics;
+  metrics::SerializationUtils::ReadMetricsFromFile(
+      metrics::kMetricsEventsFilePath, &metrics);
+
+  for (ScopedVector<metrics::MetricSample>::const_iterator i = metrics.begin();
+       i != metrics.end(); ++i) {
+    const metrics::MetricSample* sample = *i;
+    printf("name: %s\t", sample->name().c_str());
+    printf("type: ");
+
+    switch (sample->type()) {
+      case metrics::MetricSample::CRASH:
+        printf("CRASH");
+        break;
+      case metrics::MetricSample::HISTOGRAM:
+        printf("HISTOGRAM");
+        break;
+      case metrics::MetricSample::LINEAR_HISTOGRAM:
+        printf("LINEAR_HISTOGRAM");
+        break;
+      case metrics::MetricSample::SPARSE_HISTOGRAM:
+        printf("SPARSE_HISTOGRAM");
+        break;
+      case metrics::MetricSample::USER_ACTION:
+        printf("USER_ACTION");
+        break;
+    }
+
+    printf("\n");
+  }
+
+  return 0;
+}
+
 int main(int argc, char** argv) {
   enum Mode mode = kModeSendSample;
   bool secs_to_msecs = false;
 
   // Parse arguments
   int flag;
-  while ((flag = getopt(argc, argv, "abcegstuv")) != -1) {
+  while ((flag = getopt(argc, argv, "abcdegstuv")) != -1) {
     switch (flag) {
       case 'c':
         mode = kModeHasConsent;
         break;
+      case 'd':
+        mode = kModeDumpLogs;
+        break;
       case 'e':
         mode = kModeSendEnumSample;
         break;
@@ -203,6 +250,8 @@
       return HasConsent();
     case kModeIsGuestMode:
       return IsGuestMode();
+    case kModeDumpLogs:
+      return DumpLogs();
     default:
       ShowUsage();
       return 0;
diff --git a/metricsd/serialization/serialization_utils.cc b/metricsd/serialization/serialization_utils.cc
index 6dd8258..102c940 100644
--- a/metricsd/serialization/serialization_utils.cc
+++ b/metricsd/serialization/serialization_utils.cc
@@ -96,6 +96,50 @@
   return true;
 }
 
+
+// Opens the metrics log file at |filename| in the given |mode|.
+//
+// Returns the file descriptor wrapped in a valid ScopedFD on success.
+base::ScopedFD OpenMetricsFile(const std::string& filename, mode_t mode) {
+  struct stat stat_buf;
+  int result;
+
+  result = stat(filename.c_str(), &stat_buf);
+  if (result < 0) {
+    if (errno != ENOENT)
+      DPLOG(ERROR) << filename << ": bad metrics file stat";
+
+    // Nothing to collect---try later.
+    return base::ScopedFD();
+  }
+  if (stat_buf.st_size == 0) {
+    // Also nothing to collect.
+    return base::ScopedFD();
+  }
+  base::ScopedFD fd(open(filename.c_str(), mode));
+  if (fd.get() < 0) {
+    DPLOG(ERROR) << filename << ": cannot open";
+    return base::ScopedFD();
+  }
+
+  return fd.Pass();
+}
+
+
+// Parses the contents of the metrics log file descriptor |fd| into |metrics|.
+void ReadAllMetricsFromFd(int fd, ScopedVector<MetricSample>* metrics) {
+  for (;;) {
+    std::string message;
+
+    if (!ReadMessage(fd, &message))
+      break;
+
+    scoped_ptr<MetricSample> sample = SerializationUtils::ParseSample(message);
+    if (sample)
+      metrics->push_back(sample.release());
+  }
+}
+
 }  // namespace
 
 scoped_ptr<MetricSample> SerializationUtils::ParseSample(
@@ -131,30 +175,27 @@
   return scoped_ptr<MetricSample>();
 }
 
+void SerializationUtils::ReadMetricsFromFile(
+    const std::string& filename,
+    ScopedVector<MetricSample>* metrics) {
+  base::ScopedFD fd(OpenMetricsFile(filename, O_RDONLY));
+  if (!fd.is_valid()) {
+    return;
+  }
+
+  // This processes all messages in the log.
+  ReadAllMetricsFromFd(fd.get(), metrics);
+}
+
 void SerializationUtils::ReadAndTruncateMetricsFromFile(
     const std::string& filename,
     ScopedVector<MetricSample>* metrics) {
-  struct stat stat_buf;
-  int result;
+  base::ScopedFD fd(OpenMetricsFile(filename, O_RDWR));
+  if (!fd.is_valid()) {
+    return;
+  }
 
-  result = stat(filename.c_str(), &stat_buf);
-  if (result < 0) {
-    if (errno != ENOENT)
-      DPLOG(ERROR) << filename << ": bad metrics file stat";
-
-    // Nothing to collect---try later.
-    return;
-  }
-  if (stat_buf.st_size == 0) {
-    // Also nothing to collect.
-    return;
-  }
-  base::ScopedFD fd(open(filename.c_str(), O_RDWR));
-  if (fd.get() < 0) {
-    DPLOG(ERROR) << filename << ": cannot open";
-    return;
-  }
-  result = flock(fd.get(), LOCK_EX);
+  int result = flock(fd.get(), LOCK_EX);
   if (result < 0) {
     DPLOG(ERROR) << filename << ": cannot lock";
     return;
@@ -162,16 +203,7 @@
 
   // This processes all messages in the log. When all messages are
   // read and processed, or an error occurs, truncate the file to zero size.
-  for (;;) {
-    std::string message;
-
-    if (!ReadMessage(fd.get(), &message))
-      break;
-
-    scoped_ptr<MetricSample> sample = ParseSample(message);
-    if (sample)
-      metrics->push_back(sample.release());
-  }
+  ReadAllMetricsFromFd(fd.get(), metrics);
 
   result = ftruncate(fd.get(), 0);
   if (result < 0)
diff --git a/metricsd/serialization/serialization_utils.h b/metricsd/serialization/serialization_utils.h
index 67d4675..655652d 100644
--- a/metricsd/serialization/serialization_utils.h
+++ b/metricsd/serialization/serialization_utils.h
@@ -35,7 +35,11 @@
 // deserialization was successful) or a NULL scoped_ptr.
 scoped_ptr<MetricSample> ParseSample(const std::string& sample);
 
-// Reads all samples from a file and truncate the file when done.
+// Reads all samples from a file. The file contents remain unchanged.
+void ReadMetricsFromFile(const std::string& filename,
+                         ScopedVector<MetricSample>* metrics);
+
+// Reads all samples from a file and truncates the file when done.
 void ReadAndTruncateMetricsFromFile(const std::string& filename,
                                     ScopedVector<MetricSample>* metrics);