From 438772df5a7055847d061d49a8c18e6fc77daedb Mon Sep 17 00:00:00 2001
From: "rogerta@chromium.org"
 <rogerta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Fri, 26 Feb 2010 18:08:43 +0000
Subject: Add a metrics extensions API.

See http://docs.google.com/View?id=dd4ngnpz_0dpsb8n96 for API proposal.

BUG=0
TEST=See unit tests as part of this CL

Review URL: http://codereview.chromium.org/657037

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40130 0039d316-1c4b-4281-b951-d872f2087c98
---
 chrome/browser/extensions/extension_apitest.cc     |   4 +
 .../extensions/extension_function_dispatcher.cc    |  16 +++
 .../extensions/extension_metrics_apitest.cc        | 152 ++++++++++++++++++++
 .../browser/extensions/extension_metrics_module.cc | 153 +++++++++++++++++++++
 .../browser/extensions/extension_metrics_module.h  |  65 +++++++++
 chrome/chrome_browser.gypi                         |   2 +
 chrome/chrome_tests.gypi                           |   1 +
 chrome/common/chrome_switches.cc                   |   3 +
 chrome/common/chrome_switches.h                    |   1 +
 chrome/common/extensions/api/extension_api.json    | 105 ++++++++++++++
 .../resources/renderer_extension_bindings.js       |   1 +
 .../data/extensions/api_test/metrics/manifest.json |   6 +
 .../data/extensions/api_test/metrics/test.html     |   1 +
 .../test/data/extensions/api_test/metrics/test.js  |  62 +++++++++
 .../test/test_launcher/out_of_proc_test_runner.cc  |  10 +-
 15 files changed, 580 insertions(+), 2 deletions(-)
 create mode 100644 chrome/browser/extensions/extension_metrics_apitest.cc
 create mode 100644 chrome/browser/extensions/extension_metrics_module.cc
 create mode 100644 chrome/browser/extensions/extension_metrics_module.h
 create mode 100644 chrome/test/data/extensions/api_test/metrics/manifest.json
 create mode 100644 chrome/test/data/extensions/api_test/metrics/test.html
 create mode 100644 chrome/test/data/extensions/api_test/metrics/test.js

(limited to 'chrome')

diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index 25eff22..e5a0768 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/browser.h"
 #include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/profile.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/notification_registrar.h"
 #include "chrome/test/ui_test_utils.h"
 
@@ -103,4 +104,7 @@ Extension* ExtensionApiTest::GetSingleLoadedExtension() {
 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
   ExtensionBrowserTest::SetUpCommandLine(command_line);
   test_data_dir_ = test_data_dir_.AppendASCII("api_test");
+
+  // Needed for metrics extension API tests.
+  command_line->AppendSwitch(switches::kEnableMetricsExtensionApi);
 }
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 35b73a5..8f868eb 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/extensions/extension_history_api.h"
 #include "chrome/browser/extensions/extension_i18n_api.h"
 #include "chrome/browser/extensions/extension_message_service.h"
+#include "chrome/browser/extensions/extension_metrics_module.h"
 #include "chrome/browser/extensions/extension_page_actions_module.h"
 #include "chrome/browser/extensions/extension_page_actions_module_constants.h"
 #include "chrome/browser/extensions/extension_popup_api.h"
@@ -34,6 +35,7 @@
 #include "chrome/browser/profile.h"
 #include "chrome/browser/renderer_host/render_process_host.h"
 #include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/render_messages.h"
 #include "chrome/common/result_codes.h"
 #include "chrome/common/url_constants.h"
@@ -169,6 +171,20 @@ void FactoryRegistry::ResetFunctions() {
   // Processes.
   RegisterFunction<GetProcessForTabFunction>();
 
+  // Metrics.
+  if (CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableMetricsExtensionApi)) {
+    RegisterFunction<MetricsRecordUserActionFunction>();
+    RegisterFunction<MetricsRecordValueFunction>();
+    RegisterFunction<MetricsRecordPercentageFunction>();
+    RegisterFunction<MetricsRecordCountFunction>();
+    RegisterFunction<MetricsRecordSmallCountFunction>();
+    RegisterFunction<MetricsRecordMediumCountFunction>();
+    RegisterFunction<MetricsRecordTimeFunction>();
+    RegisterFunction<MetricsRecordMediumTimeFunction>();
+    RegisterFunction<MetricsRecordLongTimeFunction>();
+  }
+
   // Test.
   RegisterFunction<ExtensionTestPassFunction>();
   RegisterFunction<ExtensionTestFailFunction>();
diff --git a/chrome/browser/extensions/extension_metrics_apitest.cc b/chrome/browser/extensions/extension_metrics_apitest.cc
new file mode 100644
index 0000000..39d342a
--- /dev/null
+++ b/chrome/browser/extensions/extension_metrics_apitest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+
+#include "base/histogram.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_registrar.h"
+
+namespace {
+
+// The tests that are run by this extension are expected to record the following
+// user actions, with the specified counts.  If the tests in test.js are
+// modified, this array may need to be updated.
+struct RecordedUserAction {
+  const char* name;  // base name of metric without extension id.
+  int count;  // number of times the metric was recorded.
+} g_user_actions[] = {
+  {"test.ua.1", 1},
+  {"test.ua.2", 2},
+};
+
+// The tests that are run by this extension are expected to record the following
+// histograms.  If the tests in test.js are modified, this array may need to be
+// updated.
+struct RecordedHistogram {
+  const char* name;  // base name of metric without extension id.
+  Histogram::ClassType type;
+  int min;
+  int max;
+  size_t buckets;
+} g_histograms[] = {
+  {"test.h.1", Histogram::HISTOGRAM, 1, 100, 50},  // custom
+  {"test.h.2", Histogram::LINEAR_HISTOGRAM, 1, 200, 50},  // custom
+  {"test.h.3", Histogram::LINEAR_HISTOGRAM, 1, 101, 102},  // percentage
+  {"test.time", Histogram::HISTOGRAM, 1, 10000, 50},
+  {"test.medium.time", Histogram::HISTOGRAM, 1, 180000, 50},
+  {"test.long.time", Histogram::HISTOGRAM, 1, 3600000, 50},
+  {"test.count", Histogram::HISTOGRAM, 1, 1000000, 50},
+  {"test.medium.count", Histogram::HISTOGRAM, 1, 10000, 50},
+  {"test.small.count", Histogram::HISTOGRAM, 1, 100, 50},
+};
+
+// Build the full name of a metrics for the given extension.  Each metric
+// is made up of the unique name within the extension followed by the
+// extension's id.
+std::string BuildFullName(const std::string& name, const Extension* extension) {
+  std::string full_name(name);
+  full_name += extension->id();
+  return full_name;
+}
+
+// This class observes and collects user action notifications that are sent
+// by the tests, so that they can be examined afterwards for correctness.
+class UserActionObserver : public NotificationObserver {
+ public:
+  UserActionObserver();
+
+  void ValidateUserActions(const Extension* extension,
+                           const RecordedUserAction* recorded,
+                           int count);
+
+  virtual void Observe(NotificationType type,
+                       const NotificationSource& source,
+                       const NotificationDetails& details);
+
+ private:
+  typedef std::map<std::string, int> UserActionCountMap;
+
+  int num_metrics() const {
+    return count_map_.size();
+  }
+
+  int GetMetricCount(const std::string& name) const {
+    UserActionCountMap::const_iterator i = count_map_.find(name);
+    return i == count_map_.end() ? -1 : i->second;
+  }
+
+  NotificationRegistrar registrar_;
+  UserActionCountMap count_map_;
+};
+
+UserActionObserver::UserActionObserver() {
+  registrar_.Add(this, NotificationType::USER_ACTION,
+                 NotificationService::AllSources());
+}
+
+void UserActionObserver::Observe(NotificationType type,
+                                 const NotificationSource& source,
+                                 const NotificationDetails& details) {
+  const char* name = *Details<const char*>(details).ptr();
+  ++(count_map_[name]);
+}
+
+void UserActionObserver::ValidateUserActions(const Extension* extension,
+                                             const RecordedUserAction* recorded,
+                                             int count) {
+  EXPECT_EQ(count, num_metrics());
+
+  for (int i = 0; i < count; ++i) {
+    const RecordedUserAction& ua = recorded[i];
+    EXPECT_EQ(ua.count, GetMetricCount(BuildFullName(ua.name, extension)));
+  }
+}
+
+void ValidateHistograms(const Extension* extension,
+                        const RecordedHistogram* recorded,
+                        int count) {
+  StatisticsRecorder::Histograms histograms;
+  StatisticsRecorder::GetHistograms(&histograms);
+
+  // Code other than the tests tun here will record some histogram values, but
+  // we will ignore those. This function validates that all the histogram we
+  // expect to see are present in the list, and that their basic info is
+  // correct.
+  for (int i = 0; i < count; ++i) {
+    const RecordedHistogram& r = recorded[i];
+    std::string name(BuildFullName(r.name, extension));
+
+    size_t j = 0;
+    for (j = 0; j < histograms.size(); ++j) {
+      scoped_refptr<Histogram> histogram(histograms[j]);
+
+      if (name == histogram->histogram_name()) {
+        EXPECT_EQ(r.type, histogram->histogram_type());
+        EXPECT_EQ(r.min, histogram->declared_min());
+        EXPECT_EQ(r.max, histogram->declared_max());
+        EXPECT_EQ(r.buckets, histogram->bucket_count());
+        break;
+      }
+    }
+    EXPECT_LT(j, histograms.size());
+  }
+}
+
+}  // anonymous namespace
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Metrics) {
+  UserActionObserver observer;
+
+  ASSERT_TRUE(RunExtensionTest("metrics")) << message_;
+  Extension* extension = GetSingleLoadedExtension();
+  ASSERT_TRUE(extension);
+
+  observer.ValidateUserActions(extension,
+                               g_user_actions,
+                               arraysize(g_user_actions));
+  ValidateHistograms(extension, g_histograms, arraysize(g_histograms));
+}
diff --git a/chrome/browser/extensions/extension_metrics_module.cc b/chrome/browser/extensions/extension_metrics_module.cc
new file mode 100644
index 0000000..30db486
--- /dev/null
+++ b/chrome/browser/extensions/extension_metrics_module.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_metrics_module.h"
+
+#include "base/histogram.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/browser/metrics/user_metrics.h"
+
+namespace {
+
+// Build the full name of a metrics for the given extension.  Each metric
+// is made up of the unique name within the extension followed by the
+// extension's id.  This keeps the metrics from one extension unique from
+// other extensions, as well as those metrics from chrome itself.
+std::string BuildMetricName(const std::string& name,
+                            const Extension* extension) {
+  std::string full_name(name);
+  full_name += extension->id();
+  return full_name;
+}
+
+}  // anonymous namespace
+
+// These extension function classes are enabled only if the
+// enable-metrics-extension-api command line switch is used.  Refer to
+// extension_function_dispatcher.cc to see how they are enabled.
+
+bool MetricsRecordUserActionFunction::RunImpl() {
+  std::string name;
+  EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_STRING));
+  EXTENSION_FUNCTION_VALIDATE(args_->GetAsString(&name));
+
+  name = BuildMetricName(name, GetExtension());
+  UserMetrics::RecordComputedAction(name, profile());
+  return true;
+}
+
+bool MetricsHistogramHelperFunction::GetNameAndSample(std::string* name,
+                                                      int* sample) {
+  EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST));
+  const ListValue* args = args_as_list();
+
+  EXTENSION_FUNCTION_VALIDATE(args->GetString(0, name));
+  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(1, sample));
+  return true;
+}
+
+bool MetricsHistogramHelperFunction::RecordValue(const std::string& name,
+                                                 Histogram::ClassType type,
+                                                 int min,
+                                                 int max,
+                                                 size_t buckets,
+                                                 int sample) {
+  std::string full_name = BuildMetricName(name, GetExtension());
+  scoped_refptr<Histogram> counter;
+  if (type == Histogram::LINEAR_HISTOGRAM) {
+    counter = LinearHistogram::FactoryGet(full_name,
+                                          min,
+                                          max,
+                                          buckets,
+                                          Histogram::kUmaTargetedHistogramFlag);
+  } else {
+    counter = Histogram::FactoryGet(full_name,
+                                    min,
+                                    max,
+                                    buckets,
+                                    Histogram::kUmaTargetedHistogramFlag);
+  }
+
+  counter->Add(sample);
+  return true;
+}
+
+bool MetricsRecordValueFunction::RunImpl() {
+  EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST));
+  const ListValue* args = args_as_list();
+
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(1, &sample));
+
+  // Get the histogram parameters from the metric type object.
+  DictionaryValue* metric_type;
+  EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(0, &metric_type));
+
+  std::string name;
+  std::string type;
+  int min;
+  int max;
+  int buckets;
+  EXTENSION_FUNCTION_VALIDATE(metric_type->GetString(L"metricName", &name));
+  EXTENSION_FUNCTION_VALIDATE(metric_type->GetString(L"type", &type));
+  EXTENSION_FUNCTION_VALIDATE(metric_type->GetInteger(L"min", &min));
+  EXTENSION_FUNCTION_VALIDATE(metric_type->GetInteger(L"max", &max));
+  EXTENSION_FUNCTION_VALIDATE(metric_type->GetInteger(L"buckets", &buckets));
+
+  Histogram::ClassType histogram_type(type == "histogram-linear" ?
+      Histogram::LINEAR_HISTOGRAM : Histogram::HISTOGRAM);
+  return RecordValue(name, histogram_type, min, max, buckets, sample);
+}
+
+bool MetricsRecordPercentageFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  return RecordValue(name, Histogram::LINEAR_HISTOGRAM, 1, 101, 102, sample);
+}
+
+bool MetricsRecordCountFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  return RecordValue(name, Histogram::HISTOGRAM, 1, 1000000, 50, sample);
+}
+
+bool MetricsRecordSmallCountFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  return RecordValue(name, Histogram::HISTOGRAM, 1, 100, 50, sample);
+}
+
+bool MetricsRecordMediumCountFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  return RecordValue(name, Histogram::HISTOGRAM, 1, 10000, 50, sample);
+}
+
+bool MetricsRecordTimeFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  static const int kTenSecMs = 10 * 1000;
+  return RecordValue(name, Histogram::HISTOGRAM, 1, kTenSecMs, 50, sample);
+}
+
+bool MetricsRecordMediumTimeFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  static const int kThreeMinMs = 3 * 60 * 1000;
+  return RecordValue(name, Histogram::HISTOGRAM, 1, kThreeMinMs, 50, sample);
+}
+
+bool MetricsRecordLongTimeFunction::RunImpl() {
+  std::string name;
+  int sample;
+  EXTENSION_FUNCTION_VALIDATE(GetNameAndSample(&name, &sample));
+  static const int kOneHourMs = 60 * 60 * 1000;
+  return RecordValue(name, Histogram::HISTOGRAM, 1, kOneHourMs, 50, sample);
+}
diff --git a/chrome/browser/extensions/extension_metrics_module.h b/chrome/browser/extensions/extension_metrics_module.h
new file mode 100644
index 0000000..c2ffdd2
--- /dev/null
+++ b/chrome/browser/extensions/extension_metrics_module.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_METRICS_MODULE_H__
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_METRICS_MODULE_H__
+
+#include <string>
+
+#include "base/histogram.h"
+#include "chrome/browser/extensions/extension_function.h"
+
+class MetricsRecordUserActionFunction : public SyncExtensionFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordUserAction")
+};
+
+class MetricsHistogramHelperFunction : public SyncExtensionFunction {
+ protected:
+  bool GetNameAndSample(std::string* name, int* sample);
+  virtual bool RecordValue(const std::string& name, Histogram::ClassType type,
+      int min, int max, size_t buckets, int sample);
+};
+
+class MetricsRecordValueFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordValue")
+};
+
+class MetricsRecordPercentageFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordPercentage")
+};
+
+class MetricsRecordCountFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordCount")
+};
+
+class MetricsRecordSmallCountFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordSmallCount")
+};
+
+class MetricsRecordMediumCountFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordMediumCount")
+};
+
+class MetricsRecordTimeFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordTime")
+};
+
+class MetricsRecordMediumTimeFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordMediumTime")
+};
+
+class MetricsRecordLongTimeFunction : public MetricsHistogramHelperFunction {
+  virtual bool RunImpl();
+  DECLARE_EXTENSION_FUNCTION_NAME("metrics.recordLongTime")
+};
+
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_METRICS_MODULE_H__
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index b90d4ca..0616843 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -880,6 +880,8 @@
         'browser/extensions/extension_install_ui.h',
         'browser/extensions/extension_message_service.cc',
         'browser/extensions/extension_message_service.h',
+        'browser/extensions/extension_metrics_module.cc',
+        'browser/extensions/extension_metrics_module.h',
         'browser/extensions/extension_page_actions_module.cc',
         'browser/extensions/extension_page_actions_module.h',
         'browser/extensions/extension_page_actions_module_constants.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 9a506e0..6685622 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1160,6 +1160,7 @@
         'browser/extensions/extension_javascript_url_apitest.cc',
         'browser/extensions/extension_management_tests.cc',
         'browser/extensions/extension_messages_apitest.cc',
+        'browser/extensions/extension_metrics_apitest.cc',
         'browser/extensions/extension_override_apitest.cc',
         'browser/extensions/extension_processes_apitest.cc',
         'browser/extensions/extension_startup_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 7d16f0e..29e22c4 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -207,6 +207,9 @@ const char kEnableBenchmarking[]            = "enable-benchmarking";
 const char kEnableExperimentalExtensionApis[] =
     "enable-experimental-extension-apis";
 
+// Enable metrics extension API.
+const char kEnableMetricsExtensionApi[]    = "enable-metrics-extension-api";
+
 // Enable experimental WebGL support.
 const char kEnableExperimentalWebGL[]       = "enable-webgl";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 89aa2b0..5ab8178 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -74,6 +74,7 @@ extern const char kDomAutomationController[];
 extern const char kDumpHistogramsOnExit[];
 extern const char kEnableBenchmarking[];
 extern const char kEnableExperimentalExtensionApis[];
+extern const char kEnableMetricsExtensionApi[];
 extern const char kEnableExperimentalWebGL[];
 extern const char kEnableExtensionApps[];
 extern const char kEnableExtensionTimelineApi[];
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index 58894e4..cf28b4f 100755
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -2256,6 +2256,111 @@
     "events": []
   },
   {
+    "namespace": "metrics",
+    "types": [
+      {
+        "id": "MetricType",
+        "type":  "object",
+        "description": "Describes the type of metric that is to be collected.",
+        "properties": {
+          "metricName": {"type": "string", "description": "A unique name within the extension for the metric."},
+          "type": {
+            "type": "string",
+            "enum": ["histogram-log", "histogram-linear"],
+            "description": "The type of metric, such as 'histogram-log' or 'histogram-linear'."
+          },
+          "min": {"type": "integer", "description": "The minimum sample value to be recoded.  Must be greater than zero."},
+          "max": {"type": "integer", "description": "The maximum sample value to be recoded."},
+          "buckets": {"type": "integer", "description": "The number of buckets to use when separating the recorded values."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "recordUserAction",
+        "type": "function",
+        "description": "Records an action performed by the user.",
+        "parameters": [
+          {"name": "name", "type": "string"}
+        ]
+      },
+      {
+        "name": "recordPercentage",
+        "type": "function",
+        "description": "Record a percentage value from 1 to 100.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordCount",
+        "type": "function",
+        "description": "Record a value than can range from 1 to 1,000,000.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordSmallCount",
+        "type": "function",
+        "description": "Record a value than can range from 1 to 100.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordMediumCount",
+        "type": "function",
+        "description": "Record a value than can range from 1 to 10,000.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordTime",
+        "type": "function",
+        "description": "Record an elapsed time of no more than 10 seconds.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordMediumTime",
+        "type": "function",
+        "description": "Record an elapsed time of no more than 3 minutes.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordLongTime",
+        "type": "function",
+        "description": "Record an elapsed time of no more than 1 hour.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordValue",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Adds a value to the given metric.",
+        "parameters": [
+          {"name": "metric", "$ref": "MetricType"},
+          {"name": "value", "type": "integer"}
+        ]
+      }
+    ],
+    "events": []
+  },
+  {
     "namespace": "test",
     "nodoc": true,
     "types": [],
diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js
index c063a32..1c69ffa 100644
--- a/chrome/renderer/resources/renderer_extension_bindings.js
+++ b/chrome/renderer/resources/renderer_extension_bindings.js
@@ -250,6 +250,7 @@ var chrome = chrome || {};
       "experimental.history",
       "experimental.popup",
       "experimental.processes",
+      "metrics",
       "pageAction",
       "pageActions",
       "tabs",
diff --git a/chrome/test/data/extensions/api_test/metrics/manifest.json b/chrome/test/data/extensions/api_test/metrics/manifest.json
new file mode 100644
index 0000000..1c45fbd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/metrics/manifest.json
@@ -0,0 +1,6 @@
+{
+  "name": "chrome.metrics",
+  "version": "0.1",
+  "description": "end-to-end browser test for chrome.metrics API",
+  "background_page": "test.html"
+}
diff --git a/chrome/test/data/extensions/api_test/metrics/test.html b/chrome/test/data/extensions/api_test/metrics/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/metrics/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/metrics/test.js b/chrome/test/data/extensions/api_test/metrics/test.js
new file mode 100644
index 0000000..2de7ba4
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/metrics/test.js
@@ -0,0 +1,62 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// metrics api test
+// browser_tests.exe --gtest_filter=ExtensionApiTest.Metrics
+
+// Any changes to the logging done in these functions should be matched
+// with the checks done in IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Metrics).
+// See extension_metrics_apitest.cc.
+chrome.test.runTests([
+  function recordUserAction() {
+    // Log a metric once.
+    chrome.metrics.recordUserAction('test.ua.1');
+
+    // Log a metric more than once.
+    chrome.metrics.recordUserAction('test.ua.2');
+    chrome.metrics.recordUserAction('test.ua.2');
+
+    chrome.test.succeed();
+  },
+
+  function recordValue() {
+    chrome.metrics.recordValue({
+      'metricName': 'test.h.1',
+      'type': 'histogram-log',
+      'min': 1,
+      'max': 100,
+      'buckets': 50
+    }, 42);
+
+    chrome.metrics.recordValue({
+      'metricName': 'test.h.2',
+      'type': 'histogram-linear',
+      'min': 1,
+      'max': 200,
+      'buckets': 50
+    }, 42);
+
+    chrome.metrics.recordPercentage('test.h.3', 42);
+    chrome.metrics.recordPercentage('test.h.3', 42);
+
+    chrome.test.succeed();
+  },
+
+  function recordTimes() {
+    chrome.metrics.recordTime('test.time', 42);
+    chrome.metrics.recordMediumTime('test.medium.time', 42 * 1000);
+    chrome.metrics.recordLongTime('test.long.time', 42 * 1000 * 60);
+
+    chrome.test.succeed();
+  },
+
+  function recordCounts() {
+    chrome.metrics.recordCount('test.count', 420000);
+    chrome.metrics.recordMediumCount('test.medium.count', 4200);
+    chrome.metrics.recordSmallCount('test.small.count', 42);
+
+    chrome.test.succeed();
+  }
+]);
+
diff --git a/chrome/test/test_launcher/out_of_proc_test_runner.cc b/chrome/test/test_launcher/out_of_proc_test_runner.cc
index 32fb66c..98bdeca 100644
--- a/chrome/test/test_launcher/out_of_proc_test_runner.cc
+++ b/chrome/test/test_launcher/out_of_proc_test_runner.cc
@@ -84,7 +84,7 @@ class OutOfProcTestRunnerFactory : public tests::TestRunnerFactory {
 void PrintUsage() {
   fprintf(stdout, "Runs tests using the gtest framework, each test being run in"
       " its own process.\nAny gtest flags can be specified.\n"
-      "  --single-process\n    Runs the tests and the launcher in the same "
+      "  --single_process\n    Runs the tests and the launcher in the same "
       "process. Useful for debugging a\n    specific test in a debugger\n  "
       "--help\n    Shows this message.\n  --gtest_help\n    Shows the gtest "
       "help message\n");
@@ -101,6 +101,12 @@ int main(int argc, char** argv) {
     return 0;
   }
 
+  if (command_line->HasSwitch(kSingleProcessFlag)) {
+    fprintf(stdout,
+            "\n  Did you mean --%s instead? (note underscore)\n\n",
+            kSingleProcessAltFlag);
+  }
+
   if (command_line->HasSwitch(kChildProcessFlag) ||
       command_line->HasSwitch(kSingleProcessFlag) ||
       command_line->HasSwitch(kSingleProcessAltFlag) ||
@@ -112,7 +118,7 @@ int main(int argc, char** argv) {
   fprintf(stdout,
           "Starting tests...\nIMPORTANT DEBUGGING NOTE: each test is run inside"
           " its own process.\nFor debugging a test inside a debugger, use the "
-          "--single-process and\n--gtest_filter=<your_test_name> flags.\n");
+          "--single_process and\n--gtest_filter=<your_test_name> flags.\n");
   OutOfProcTestRunnerFactory test_runner_factory;
   return tests::RunTests(test_runner_factory) ? 0 : 1;
 }
-- 
cgit v1.1