Add support for user actions to the metrics library and the metrics clients.

BUG=10696
TEST=unit tests, tested on the device through metrics_client and inspecting
the uma-events file.

Change-Id: Ie39dd8b5ab968c328993076369a4ba14cb7fcd81

Review URL: http://codereview.chromium.org/6094010
diff --git a/metrics/README b/metrics/README
index 1519648..2ecec5f 100644
--- a/metrics/README
+++ b/metrics/README
@@ -29,19 +29,9 @@
   <metrics/metrics_library.h> header file. The file is installed in
   $SYSROOT/usr/include/ when the metrics library is built and installed.
 
-- The API includes two methods:
-
-  bool MetricsLibrary::SendToUMA(const std::string& name, int sample,
-                                 int min, int max, int nbuckets)
-  sends a sample for a regular (exponential) histogram.
-
-  bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
-                                     int max)
-  sends a sample for an enumeration (linear) histogram.
-
-  Before using these methods, a MetricsLibrary object needs to be constructed
-  and initialized through its Init method. See the complete API documentation in
-  metrics_library.h under src/platform/metrics/.
+- The API is documented in metrics_library.h under src/platform/metrics/. Before
+  using the API methods, a MetricsLibrary object needs to be constructed and
+  initialized through its Init method.
 
   For more information on the C API see c_metrics_library.h.
 
@@ -88,9 +78,9 @@
 ================================================================================
 
 metrics_client is a simple shell command-line utility for sending histogram
-samples. It's installed under /usr/bin on the target platform and uses
-libmetrics to send the data to Chrome. The utility is useful for generating
-metrics from shell scripts.
+samples and user actions. It's installed under /usr/bin on the target platform
+and uses libmetrics to send the data to Chrome. The utility is useful for
+generating metrics from shell scripts.
 
 For usage information and command-line options, run "metrics_client" on the
 target platform or look for "Usage:" in metrics_client.cc.
diff --git a/metrics/c_metrics_library.cc b/metrics/c_metrics_library.cc
index e97f9ac..3450918 100644
--- a/metrics/c_metrics_library.cc
+++ b/metrics/c_metrics_library.cc
@@ -43,6 +43,14 @@
   return lib->SendEnumToUMA(std::string(name), sample, max);
 }
 
+extern "C" int CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+                                                  const char* action) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->SendUserActionToUMA(std::string(action));
+}
+
 extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) {
   MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
   if (lib == NULL)
diff --git a/metrics/c_metrics_library.h b/metrics/c_metrics_library.h
index e691ad6..9500aad 100644
--- a/metrics/c_metrics_library.h
+++ b/metrics/c_metrics_library.h
@@ -28,6 +28,10 @@
 int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
                                  const char* name, int sample, int max);
 
+// C wrapper for MetricsLibrary::SendUserActionToUMA.
+int CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+                                       const char* action);
+
 // C wrapper for MetricsLibrary::AreMetricsEnabled.
 int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle);
 
diff --git a/metrics/metrics_client.cc b/metrics/metrics_client.cc
index ce22e98..5aedd8b 100644
--- a/metrics/metrics_client.cc
+++ b/metrics/metrics_client.cc
@@ -11,6 +11,7 @@
   fprintf(stderr,
           "Usage:  metrics_client [-ab] [-t] name sample min max nbuckets\n"
           "        metrics_client [-ab] -e   name sample max\n"
+          "        metrics_client -u action\n"
           "        metrics_client [-cg]\n"
           "\n"
           "  default: send metric with integer values to Chrome only\n"
@@ -20,7 +21,8 @@
           "  -c: return exit status 0 if user consents to stats, 1 otherwise\n"
           "  -e: send linear/enumeration histogram data\n"
           "  -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
-          "  -t: convert sample from double seconds to int milliseconds\n");
+          "  -t: convert sample from double seconds to int milliseconds\n"
+          "  -u: send a user action to Chrome\n");
   exit(1);
 }
 
@@ -59,6 +61,14 @@
   return 0;
 }
 
+static int SendUserAction(char* argv[], int action_index) {
+  const char* action = argv[action_index];
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  metrics_lib.SendUserActionToUMA(action);
+  return 0;
+}
+
 static int HasConsent() {
   MetricsLibrary metrics_lib;
   metrics_lib.Init();
@@ -74,6 +84,7 @@
 int main(int argc, char** argv) {
   enum Mode {
     kModeSendStats,
+    kModeSendUserAction,
     kModeHasConsent,
     kModeIsGuestMode
   } mode = kModeSendStats;
@@ -85,7 +96,7 @@
 
   // Parse arguments
   int flag;
-  while ((flag = getopt(argc, argv, "abcegt")) != -1) {
+  while ((flag = getopt(argc, argv, "abcegtu")) != -1) {
     switch (flag) {
       case 'a':
         mode = kModeSendStats;
@@ -109,18 +120,23 @@
       case 't':
         secs_to_msecs = true;
         break;
+      case 'u':
+        mode = kModeSendUserAction;
+        break;
       default:
         print_usage = true;
         break;
     }
   }
-  int name_index = optind;
+  int arg_index = optind;
 
   int expected_args = 0;
   if (mode == kModeSendStats)
     expected_args = send_enum ? 3 : 5;
+  else if (mode == kModeSendUserAction)
+    expected_args = 1;
 
-  if ((name_index + expected_args) != argc) {
+  if ((arg_index + expected_args) != argc) {
     ShowUsage();
   }
 
@@ -130,11 +146,13 @@
         ShowUsage();
       }
       return SendStats(argv,
-                       name_index,
+                       arg_index,
                        send_enum,
                        secs_to_msecs,
                        send_to_autotest,
                        send_to_chrome);
+    case kModeSendUserAction:
+      return SendUserAction(argv, arg_index);
     case kModeHasConsent:
       return HasConsent();
     case kModeIsGuestMode:
diff --git a/metrics/metrics_library.cc b/metrics/metrics_library.cc
index f720972..05e63ac 100644
--- a/metrics/metrics_library.cc
+++ b/metrics/metrics_library.cc
@@ -221,7 +221,6 @@
       FormatChromeMessage(kBufferSize, message,
                           "histogram%c%s %d %d %d %d", '\0',
                           name.c_str(), sample, min, max, nbuckets);
-
   if (message_length < 0)
     return false;
 
@@ -237,7 +236,19 @@
       FormatChromeMessage(kBufferSize, message,
                           "linearhistogram%c%s %d %d", '\0',
                           name.c_str(), sample, max);
+  if (message_length < 0)
+    return false;
 
+  // Send the message.
+  return SendMessageToChrome(message_length, message);
+}
+
+bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
+  // Format the message.
+  char message[kBufferSize];
+  int32_t message_length =
+      FormatChromeMessage(kBufferSize, message,
+                          "useraction%c%s", '\0', action.c_str());
   if (message_length < 0)
     return false;
 
diff --git a/metrics/metrics_library.h b/metrics/metrics_library.h
index fb31c20..b9b817a 100644
--- a/metrics/metrics_library.h
+++ b/metrics/metrics_library.h
@@ -69,6 +69,15 @@
   // normal, while 100 is high).
   bool SendEnumToUMA(const std::string& name, int sample, int max);
 
+  // Sends a user action to Chrome for transport to UMA and returns true on
+  // success. This method results in the equivalent of an asynchronous
+  // non-blocking RPC to UserMetrics::RecordAction (see the comments in
+  // chrome/browser/chromeos/external_metrics.cc and
+  // chrome/browser/metrics/user_metrics.h on how to register new user actions).
+  //
+  // |action| is the user-generated event (e.g., "MuteKeyPressed").
+  bool SendUserActionToUMA(const std::string& action);
+
   // Sends to Autotest and returns true on success.
   static bool SendToAutotest(const std::string& name, int value);
 
diff --git a/metrics/metrics_library_test.cc b/metrics/metrics_library_test.cc
index 0cd695b..3e49f69 100644
--- a/metrics/metrics_library_test.cc
+++ b/metrics/metrics_library_test.cc
@@ -214,6 +214,23 @@
   EXPECT_FALSE(file_util::PathExists(kTestUMAEventsFile));
 }
 
+TEST_F(MetricsLibraryTest, SendUserActionToUMA) {
+  char buf[100];
+  const int kLen = 30;
+  EXPECT_TRUE(lib_.SendUserActionToUMA("SomeKeyPressed"));
+  EXPECT_EQ(kLen, file_util::ReadFile(kTestUMAEventsFile, buf, 100));
+
+  char exp[kLen];
+  sprintf(exp, "%c%c%c%cuseraction%cSomeKeyPressed", kLen, 0, 0, 0, 0);
+  EXPECT_EQ(0, memcmp(exp, buf, kLen));
+}
+
+TEST_F(MetricsLibraryTest, SendUserActionToUMANotEnabled) {
+  SetMetricsEnabled(false);
+  EXPECT_TRUE(lib_.SendUserActionToUMA("SomeOtherKeyPressed"));
+  EXPECT_FALSE(file_util::PathExists(kTestUMAEventsFile));
+}
+
 class CMetricsLibraryTest : public testing::Test {
  protected:
   virtual void SetUp() {
@@ -268,6 +285,17 @@
   EXPECT_EQ(0, memcmp(exp, buf, kLen));
 }
 
+TEST_F(CMetricsLibraryTest, SendUserActionToUMA) {
+  char buf[100];
+  const int kLen = 30;
+  EXPECT_TRUE(CMetricsLibrarySendUserActionToUMA(lib_, "SomeKeyPressed"));
+  EXPECT_EQ(kLen, file_util::ReadFile(kTestUMAEventsFile, buf, 100));
+
+  char exp[kLen];
+  sprintf(exp, "%c%c%c%cuseraction%cSomeKeyPressed", kLen, 0, 0, 0, 0);
+  EXPECT_EQ(0, memcmp(exp, buf, kLen));
+}
+
 int main(int argc, char** argv) {
   testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();