Domain Reliability / Navigation Error Logging, part 1

This is just the logging portion of Domain Reliability; the uploading
and configuration parts will come later.

BUG=

Review URL: https://codereview.chromium.org/132283009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257815 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index cf73450..294b410 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -14,6 +14,7 @@
   "+components/autofill/core/common",
   "+components/breakpad",
   "+components/dom_distiller",
+  "+components/domain_reliability",
   "+components/keyed_service",
   "+components/language_usage_metrics",
   "+components/nacl/browser",
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index 6c99b19..d25f629b2 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -34,6 +34,7 @@
 #include "chrome/browser/task_manager/task_manager.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
+#include "components/domain_reliability/monitor.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -347,6 +348,7 @@
       enable_do_not_track_(NULL),
       force_google_safe_search_(NULL),
       url_blacklist_manager_(NULL),
+      domain_reliability_monitor_(NULL),
       received_content_length_(0),
       original_content_length_(0) {
   DCHECK(event_router);
@@ -518,6 +520,8 @@
 
 void ChromeNetworkDelegate::OnBeforeRedirect(net::URLRequest* request,
                                              const GURL& new_location) {
+  if (domain_reliability_monitor_)
+    domain_reliability_monitor_->OnBeforeRedirect(request);
   ExtensionWebRequestEventRouter::GetInstance()->OnBeforeRedirect(
       profile_, extension_info_map_.get(), request, new_location);
 }
@@ -612,6 +616,8 @@
   } else {
     NOTREACHED();
   }
+  if (domain_reliability_monitor_)
+    domain_reliability_monitor_->OnCompleted(request, started);
   ForwardProxyErrors(request, event_router_.get(), profile_);
 
   ForwardRequestStatus(REQUEST_DONE, request, profile_);
diff --git a/chrome/browser/net/chrome_network_delegate.h b/chrome/browser/net/chrome_network_delegate.h
index f6c8361..6adf283 100644
--- a/chrome/browser/net/chrome_network_delegate.h
+++ b/chrome/browser/net/chrome_network_delegate.h
@@ -32,6 +32,10 @@
 class Predictor;
 }
 
+namespace domain_reliability {
+class DomainReliabilityMonitor;
+}  // namespace domain_reliability
+
 namespace extensions {
 class EventRouterForwarder;
 class InfoMap;
@@ -96,6 +100,12 @@
     force_google_safe_search_ = force_google_safe_search;
   }
 
+  void set_domain_reliability_monitor(
+      domain_reliability::DomainReliabilityMonitor*
+          domain_reliability_monitor) {
+    domain_reliability_monitor_ = domain_reliability_monitor;
+  }
+
   // Adds the Client Hints header to HTTP requests.
   void SetEnableClientHints();
 
@@ -194,6 +204,7 @@
 
   // Weak, owned by our owner.
   const policy::URLBlacklistManager* url_blacklist_manager_;
+  domain_reliability::DomainReliabilityMonitor* domain_reliability_monitor_;
 
   // When true, allow access to all file:// URLs.
   static bool g_allow_file_access_;
diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc
index 9649f41..dd51909 100644
--- a/chrome/browser/profiles/profile_impl_io_data.cc
+++ b/chrome/browser/profiles/profile_impl_io_data.cc
@@ -33,6 +33,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
+#include "components/domain_reliability/monitor.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/cookie_store_factory.h"
 #include "content/public/browser/notification_service.h"
@@ -80,6 +81,15 @@
 #endif
 }
 
+bool IsDomainReliabilityMonitoringEnabled() {
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kDisableDomainReliability))
+    return false;
+  if (command_line->HasSwitch(switches::kEnableDomainReliability))
+    return true;
+  return base::FieldTrialList::FindFullName("DomRel-Enable") == "enable";
+}
+
 }  // namespace
 
 using content::BrowserThread;
@@ -495,6 +505,13 @@
   media_request_context_.reset(InitializeMediaRequestContext(main_context,
                                                              details));
 
+  if (IsDomainReliabilityMonitoringEnabled()) {
+    domain_reliability_monitor_.reset(
+        new domain_reliability::DomainReliabilityMonitor());
+    network_delegate()->set_domain_reliability_monitor(
+        domain_reliability_monitor_.get());
+  }
+
   lazy_params_.reset();
 }
 
diff --git a/chrome/browser/profiles/profile_impl_io_data.h b/chrome/browser/profiles/profile_impl_io_data.h
index 94af627..e03754093 100644
--- a/chrome/browser/profiles/profile_impl_io_data.h
+++ b/chrome/browser/profiles/profile_impl_io_data.h
@@ -22,6 +22,10 @@
 class CookieCryptoDelegate;
 }  // namespace content
 
+namespace domain_reliability {
+class DomainReliabilityMonitor;
+}  // namespace domain_reliability
+
 namespace net {
 class FtpTransactionFactory;
 class HttpServerProperties;
@@ -210,6 +214,9 @@
   mutable scoped_ptr<net::URLRequestJobFactory> main_job_factory_;
   mutable scoped_ptr<net::URLRequestJobFactory> extensions_job_factory_;
 
+  mutable scoped_ptr<domain_reliability::DomainReliabilityMonitor>
+      domain_reliability_monitor_;
+
   // Parameters needed for isolated apps.
   base::FilePath profile_path_;
   int app_cache_max_size_;
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 80a71a55..be98431 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -38,6 +38,7 @@
         '../components/component_strings.gyp:component_strings',
         '../components/components.gyp:autofill_core_browser',
         '../components/components.gyp:cloud_devices',
+        '../components/components.gyp:domain_reliability',
         '../components/components.gyp:navigation_metrics',
         '../components/components.gyp:os_crypt',
         '../components/components.gyp:password_manager_core_browser',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 9d47608..0c5c4300 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -299,6 +299,9 @@
 // Disables retrieval of PAC URLs from DHCP as per the WPAD standard.
 const char kDisableDhcpWpad[]               = "disable-dhcp-wpad";
 
+// Disables Domain Reliability Monitoring.
+const char kDisableDomainReliability[]      = "disable-domain-reliability";
+
 // Disable extensions.
 const char kDisableExtensions[]             = "disable-extensions";
 
@@ -526,6 +529,9 @@
 // Enables the DOM distiller.
 const char kEnableDomDistiller[]               = "enable-dom-distiller";
 
+// Enables Domain Reliability Monitoring.
+const char kEnableDomainReliability[]          = "enable-domain-reliability";
+
 // Enable Enhanced Bookmarks.
 const char kEnhancedBookmarksExperiment[] = "enhanced-bookmarks-experiment";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index f0e0573..3cae99b 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -93,6 +93,7 @@
 extern const char kDisableDeviceDiscoveryNotifications[];
 extern const char kDisableDhcpWpad[];
 extern const char kDisableDnsProbes[];
+extern const char kDisableDomainReliability[];
 extern const char kDisableExtensionsFileAccessCheck[];
 extern const char kDisableExtensionsHttpThrottling[];
 extern const char kDisableExtensionsResourceWhitelist[];
@@ -151,7 +152,8 @@
 extern const char kEnableDevToolsExperiments[];
 extern const char kEnableDeviceDiscoveryNotifications[];
 extern const char kEnableDomDistiller[];
-extern const char kEnhancedBookmarksExperiment[];
+extern const char kEnableDomainReliability[];
+extern const char kEnableEnhancedBookmarks[];
 extern const char kEnableEphemeralApps[];
 extern const char kEnableExtensionActivityLogging[];
 extern const char kEnableExtensionActivityLogTesting[];
@@ -207,6 +209,7 @@
 extern const char kEnableUserAlternateProtocolPorts[];
 extern const char kEnableWatchdog[];
 extern const char kEnableWebSocketOverSpdy[];
+extern const char kEnhancedBookmarksExperiment[];
 extern const char kExplicitlyAllowedPorts[];
 extern const char kExtensionsInstallVerification[];
 extern const char kExtensionsNotWebstore[];
diff --git a/components/OWNERS b/components/OWNERS
index 104cb19..427b187 100644
--- a/components/OWNERS
+++ b/components/OWNERS
@@ -18,6 +18,11 @@
 per-file dom_distiller*[email protected]
 per-file dom_distiller*[email protected]
 
+per-file [email protected]
+per-file [email protected]
+per-file [email protected]
+per-file [email protected]
+
 per-file [email protected]
 per-file [email protected]
 per-file [email protected]
diff --git a/components/components.gyp b/components/components.gyp
index ff1222fc..aa24986 100644
--- a/components/components.gyp
+++ b/components/components.gyp
@@ -15,6 +15,7 @@
     'breakpad.gypi',
     'cloud_devices.gypi',
     'dom_distiller.gypi',
+    'domain_reliability.gypi',
     'json_schema.gypi',
     'keyed_service.gypi',
     'language_usage_metrics.gypi',
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index d15b5c7..b2b32989 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -64,6 +64,11 @@
             'dom_distiller/core/dom_distiller_store_unittest.cc',
             'dom_distiller/core/task_tracker_unittest.cc',
             'dom_distiller/core/url_utils_unittest.cc',
+            'domain_reliability/context_unittest.cc',
+            'domain_reliability/monitor_unittest.cc',
+            'domain_reliability/test_util.cc',
+            'domain_reliability/test_util.h',
+            'domain_reliability/util_unittest.cc',
             'json_schema/json_schema_validator_unittest.cc',
             'json_schema/json_schema_validator_unittest_base.cc',
             'json_schema/json_schema_validator_unittest_base.h',
@@ -165,8 +170,8 @@
             'components.gyp:dom_distiller_core',
             'components.gyp:dom_distiller_test_support',
 
-            # Dependencies of os_crypt
-            'components.gyp:os_crypt',
+            # Dependencies of domain_reliability
+            'components.gyp:domain_reliability',
 
             # Dependencies of json_schema
             'components.gyp:json_schema',
@@ -177,6 +182,9 @@
             # Dependencies of language_usage_metrics
             'components.gyp:language_usage_metrics',
 
+            # Dependencies of os_crypt
+            'components.gyp:os_crypt',
+
             # Dependencies of password_manager
             'components.gyp:password_manager_core_browser',
             'components.gyp:password_manager_core_browser_test_support',
diff --git a/components/domain_reliability.gypi b/components/domain_reliability.gypi
new file mode 100644
index 0000000..0c5052fd
--- /dev/null
+++ b/components/domain_reliability.gypi
@@ -0,0 +1,37 @@
+# Copyright 2014 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'domain_reliability',
+      'type': '<(component)',
+      'dependencies': [
+        '../base/base.gyp:base',
+        '../content/content.gyp:content_browser',
+        '../net/net.gyp:net',
+        '../url/url.gyp:url_lib',
+      ],
+      'include_dirs': [
+        '..',
+      ],
+      'defines': [
+        'DOMAIN_RELIABILITY_IMPLEMENTATION',
+      ],
+      'sources': [
+        'domain_reliability/beacon.cc',
+        'domain_reliability/beacon.h',
+        'domain_reliability/config.cc',
+        'domain_reliability/config.h',
+        'domain_reliability/context.cc',
+        'domain_reliability/context.h',
+        'domain_reliability/domain_reliability_export.h',
+        'domain_reliability/monitor.cc',
+        'domain_reliability/monitor.h',
+        'domain_reliability/util.cc',
+        'domain_reliability/util.h',
+      ],
+    },
+  ],
+}
diff --git a/components/domain_reliability/DEPS b/components/domain_reliability/DEPS
new file mode 100644
index 0000000..70cc3c5
--- /dev/null
+++ b/components/domain_reliability/DEPS
@@ -0,0 +1,9 @@
+# Copyright 2014 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_rules = [
+  "+content/public/browser",
+  "+content/public/test",
+  "+net",
+]
diff --git a/components/domain_reliability/OWNERS b/components/domain_reliability/OWNERS
new file mode 100644
index 0000000..07a5e14
--- /dev/null
+++ b/components/domain_reliability/OWNERS
@@ -0,0 +1,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/components/domain_reliability/beacon.cc b/components/domain_reliability/beacon.cc
new file mode 100644
index 0000000..8b738114
--- /dev/null
+++ b/components/domain_reliability/beacon.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 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 "components/domain_reliability/beacon.h"
+
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+namespace domain_reliability {
+
+using base::Value;
+using base::DictionaryValue;
+
+DomainReliabilityBeacon::DomainReliabilityBeacon() {}
+DomainReliabilityBeacon::~DomainReliabilityBeacon() {}
+
+Value* DomainReliabilityBeacon::ToValue(base::TimeTicks upload_time) const {
+  DictionaryValue* beacon_value = new DictionaryValue();
+  beacon_value->SetString("status", status);
+  if (chrome_error != net::OK) {
+    DictionaryValue* failure_value = new DictionaryValue();
+    failure_value->SetString("custom_error",
+                             net::ErrorToString(chrome_error));
+    beacon_value->Set("failure_data", failure_value);
+  }
+  beacon_value->SetString("server_ip", server_ip);
+  if (http_response_code >= 0)
+    beacon_value->SetInteger("http_response_code", http_response_code);
+  beacon_value->SetInteger("request_elapsed_ms",
+                           elapsed.InMilliseconds());
+  beacon_value->SetInteger("request_age_ms",
+                           (upload_time - start_time).InMilliseconds());
+  // TODO(ttuttle): Implement protocol and dns_resolver_ip[s] fields.
+
+  return beacon_value;
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/beacon.h b/components/domain_reliability/beacon.h
new file mode 100644
index 0000000..42cfb1c
--- /dev/null
+++ b/components/domain_reliability/beacon.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_BEACON_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_BEACON_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "components/domain_reliability/domain_reliability_export.h"
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace domain_reliability {
+
+// The per-request data that is uploaded to the Domain Reliability collector.
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityBeacon {
+ public:
+  DomainReliabilityBeacon();
+  ~DomainReliabilityBeacon();
+
+  // Converts the Beacon to JSON format for uploading. Calculates the age
+  // relative to an upload time of |upload_time|.
+  base::Value* ToValue(base::TimeTicks upload_time) const;
+
+  // Status string (e.g. "ok", "dns.nxdomain", "http.403").
+  std::string status;
+  // Net error code.  Encoded as a string in the final JSON.
+  int chrome_error;
+  // IP address of the server the request went to.
+  std::string server_ip;
+  // HTTP response code returned by the server, or -1 if none was received.
+  int http_response_code;
+  // Elapsed time between starting and completing the request.
+  base::TimeDelta elapsed;
+  // Start time of the request.  Encoded as the request age in the final JSON.
+  base::TimeTicks start_time;
+
+  // Okay to copy and assign :)
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_BEACON_H_
diff --git a/components/domain_reliability/config.cc b/components/domain_reliability/config.cc
new file mode 100644
index 0000000..03d45972
--- /dev/null
+++ b/components/domain_reliability/config.cc
@@ -0,0 +1,110 @@
+// Copyright 2014 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 "components/domain_reliability/config.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_value_converter.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+
+namespace {
+
+bool ConvertURL(const base::StringPiece& string_piece, GURL* url) {
+  *url = GURL(string_piece.as_string());
+  return url->is_valid();
+}
+
+}  // namespace
+
+namespace domain_reliability {
+
+DomainReliabilityConfig::Resource::Resource() {}
+
+DomainReliabilityConfig::Resource::~Resource() {}
+
+bool DomainReliabilityConfig::Resource::MatchesUrlString(
+    const std::string& url_string) const {
+  ScopedVector<std::string>::const_iterator it;
+
+  for (it = url_patterns.begin(); it != url_patterns.end(); it++)
+    if (MatchPattern(url_string, **it))
+      return true;
+
+  return false;
+}
+
+bool DomainReliabilityConfig::Resource::DecideIfShouldReportRequest(
+    bool success) const {
+  double sample_rate = success ? success_sample_rate : failure_sample_rate;
+  return base::RandDouble() < sample_rate;
+}
+
+// static
+void DomainReliabilityConfig::Resource::RegisterJSONConverter(
+    base::JSONValueConverter<DomainReliabilityConfig::Resource>* converter) {
+  converter->RegisterStringField("resource_name", &Resource::name);
+  converter->RegisterRepeatedString("url_patterns", &Resource::url_patterns);
+  converter->RegisterDoubleField("success_sample_rate",
+                                 &Resource::success_sample_rate);
+  converter->RegisterDoubleField("failure_sample_rate",
+                                 &Resource::failure_sample_rate);
+}
+
+DomainReliabilityConfig::Collector::Collector() {}
+
+DomainReliabilityConfig::Collector::~Collector() {}
+
+// static
+void DomainReliabilityConfig::Collector::RegisterJSONConverter(
+    base::JSONValueConverter<DomainReliabilityConfig::Collector>* converter) {
+  converter->RegisterCustomField<GURL>("upload_url", &Collector::upload_url,
+                                       &ConvertURL);
+}
+
+DomainReliabilityConfig::DomainReliabilityConfig() {}
+
+DomainReliabilityConfig::~DomainReliabilityConfig() {}
+
+// static
+scoped_ptr<const DomainReliabilityConfig> DomainReliabilityConfig::FromJSON(
+    const base::StringPiece& json) {
+  base::Value* value = base::JSONReader::Read(json);
+  if (!value)
+    return scoped_ptr<const DomainReliabilityConfig>();
+
+  DomainReliabilityConfig* config = new DomainReliabilityConfig();
+  base::JSONValueConverter<DomainReliabilityConfig> converter;
+  if (!converter.Convert(*value, config)) {
+    return scoped_ptr<const DomainReliabilityConfig>();
+  }
+
+  return scoped_ptr<const DomainReliabilityConfig>(config);
+}
+
+int DomainReliabilityConfig::GetResourceIndexForUrl(const GURL& url) const {
+  const std::string& url_string = url.spec();
+
+  for (size_t i = 0; i < resources.size(); ++i) {
+    if (resources[i]->MatchesUrlString(url_string))
+      return static_cast<int>(i);
+  }
+
+  return -1;
+}
+
+// static
+void DomainReliabilityConfig::RegisterJSONConverter(
+    base::JSONValueConverter<DomainReliabilityConfig>* converter) {
+  converter->RegisterStringField("config_version",
+                                 &DomainReliabilityConfig::config_version);
+  converter->RegisterStringField("monitored_domain",
+                                 &DomainReliabilityConfig::domain);
+  converter->RegisterRepeatedMessage("monitored_resources",
+                                     &DomainReliabilityConfig::resources);
+  converter->RegisterRepeatedMessage("collectors",
+                                     &DomainReliabilityConfig::collectors);
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/config.h b/components/domain_reliability/config.h
new file mode 100644
index 0000000..6453303
--- /dev/null
+++ b/components/domain_reliability/config.h
@@ -0,0 +1,112 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_CONFIG_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_CONFIG_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/json/json_value_converter.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "components/domain_reliability/domain_reliability_export.h"
+#include "url/gurl.h"
+
+namespace domain_reliability {
+
+// The configuration that controls which requests are measured and reported,
+// with what frequency, and where the beacons are uploaded.
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityConfig {
+ public:
+  // A particular resource named in the config -- includes a set of URL
+  // patterns that the resource will match, along with sample rates for
+  // successful and unsuccessful requests.
+  class DOMAIN_RELIABILITY_EXPORT Resource {
+   public:
+    Resource();
+    ~Resource();
+
+    // Returns whether |url_string| matches at least one of the |url_patterns|
+    // in this Resource.
+    bool MatchesUrlString(const std::string& url_string) const;
+
+    // Returns whether a request (that was successful if |success| is true)
+    // should be reported (with a full beacon). (The output is random; it
+    // compares a random number to |success_sample_rate| or
+    // |failure_sample_rate|.)
+    bool DecideIfShouldReportRequest(bool success) const;
+
+    // Registers with the JSONValueConverter so it will know how to convert the
+    // JSON for a named resource into the struct.
+    static void RegisterJSONConverter(
+        base::JSONValueConverter<Resource>* converter);
+
+    // Name of the Resource, as will be reported in uploads.
+    std::string name;
+
+    // List of URL patterns to assign requests to this Resource.
+    ScopedVector<std::string> url_patterns;
+
+    // Sample rates for successful and unsuccessful requests, respectively.
+    // 0.0 reports no requests, and 1.0 reports every request.
+    double success_sample_rate;
+    double failure_sample_rate;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Resource);
+  };
+
+  // A particular endpoint for report uploads. Includes the URL to upload
+  // reports to. May include a verification URL or backoff/load management
+  // configuration in the future.
+  struct DOMAIN_RELIABILITY_EXPORT Collector {
+   public:
+    Collector();
+    ~Collector();
+
+    // Registers with the JSONValueConverter so it will know how to convert the
+    // JSON for a collector into the struct.
+    static void RegisterJSONConverter(
+        base::JSONValueConverter<Collector>* converter);
+
+    GURL upload_url;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Collector);
+  };
+
+  DomainReliabilityConfig();
+  ~DomainReliabilityConfig();
+
+  // Uses the JSONValueConverter to parse the JSON for a config into a struct.
+  static scoped_ptr<const DomainReliabilityConfig> FromJSON(
+      const base::StringPiece& json);
+
+  // Finds the index (in resources) of the first Resource that matches a
+  // particular URL. Returns -1 if the URL is not matched by any Resources.
+  int GetResourceIndexForUrl(const GURL& url) const;
+
+  // Registers with the JSONValueConverter so it will know how to convert the
+  // JSON for a config into the struct.
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<DomainReliabilityConfig>* converter);
+
+  std::string config_version;
+  std::string domain;
+  ScopedVector<Resource> resources;
+  ScopedVector<Collector> collectors;
+
+  // TODO(ttuttle): Add config_valid_util when fetching and expiring configs
+  //                is implemented.
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DomainReliabilityConfig);
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_CONFIG_H_
diff --git a/components/domain_reliability/context.cc b/components/domain_reliability/context.cc
new file mode 100644
index 0000000..5a69689
--- /dev/null
+++ b/components/domain_reliability/context.cc
@@ -0,0 +1,206 @@
+// Copyright 2014 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 "components/domain_reliability/context.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace domain_reliability {
+
+namespace {
+const char* kReporter = "chrome";
+typedef std::deque<DomainReliabilityBeacon> BeaconDeque;
+typedef BeaconDeque::iterator BeaconIterator;
+typedef BeaconDeque::const_iterator BeaconConstIterator;
+}  // namespace
+
+const int DomainReliabilityContext::kMaxQueuedBeacons = 150;
+
+DomainReliabilityContext::DomainReliabilityContext(
+    MockableTime* time,
+    scoped_ptr<const DomainReliabilityConfig> config)
+    : config_(config.Pass()),
+      time_(time),
+      beacon_count_(0),
+      weak_factory_(this) {
+  InitializeResourceStates();
+}
+
+DomainReliabilityContext::~DomainReliabilityContext() {}
+
+void DomainReliabilityContext::AddBeacon(
+    const DomainReliabilityBeacon& beacon,
+    const GURL& url) {
+  int index = config_->GetResourceIndexForUrl(url);
+  if (index < 0)
+    return;
+  DCHECK_GT(states_.size(), static_cast<size_t>(index));
+
+  ResourceState* state = states_[index];
+  bool success = beacon.http_response_code >= 200 &&
+                 beacon.http_response_code < 400;
+  if (success)
+    ++state->successful_requests;
+  else
+    ++state->failed_requests;
+
+  VLOG(1) << "Received Beacon: "
+          << state->config->name << " "
+          << beacon.status << " "
+          << beacon.chrome_error << " "
+          << beacon.http_response_code << " "
+          << beacon.server_ip << " "
+          << beacon.elapsed.InMilliseconds() << "ms";
+
+  if (state->config->DecideIfShouldReportRequest(success)) {
+    state->beacons.push_back(beacon);
+    ++beacon_count_;
+    if (beacon_count_ > kMaxQueuedBeacons)
+      RemoveOldestBeacon();
+  }
+}
+
+void DomainReliabilityContext::GetQueuedDataForTesting(
+    int resource_index,
+    std::vector<DomainReliabilityBeacon>* beacons_out,
+    int* successful_requests_out,
+    int* failed_requests_out) const {
+  DCHECK_LE(0, resource_index);
+  DCHECK_GT(static_cast<int>(states_.size()), resource_index);
+  const ResourceState& state = *states_[resource_index];
+  if (beacons_out) {
+    beacons_out->resize(state.beacons.size());
+    std::copy(state.beacons.begin(), state.beacons.end(), beacons_out->begin());
+  }
+  if (successful_requests_out)
+    *successful_requests_out = state.successful_requests;
+  if (failed_requests_out)
+    *failed_requests_out = state.failed_requests;
+}
+
+DomainReliabilityContext::ResourceState::ResourceState(
+    DomainReliabilityContext* context,
+    const DomainReliabilityConfig::Resource* config)
+    : context(context),
+      config(config),
+      successful_requests(0),
+      failed_requests(0) {}
+
+DomainReliabilityContext::ResourceState::~ResourceState() {}
+
+scoped_ptr<Value> DomainReliabilityContext::ResourceState::ToValue(
+    base::TimeTicks upload_time) const {
+  ListValue* beacons_value = new ListValue();
+  for (BeaconConstIterator it = beacons.begin(); it != beacons.end(); ++it)
+    beacons_value->Append(it->ToValue(upload_time));
+
+  DictionaryValue* resource_value = new DictionaryValue();
+  resource_value->SetString("resource_name", config->name);
+  resource_value->SetInteger("successful_requests", successful_requests);
+  resource_value->SetInteger("failed_requests", failed_requests);
+  resource_value->Set("beacons", beacons_value);
+
+  return scoped_ptr<Value>(resource_value);
+}
+
+void DomainReliabilityContext::ResourceState::MarkUpload() {
+  uploading_beacons_size = beacons.size();
+  uploading_successful_requests = successful_requests;
+  uploading_failed_requests = failed_requests;
+}
+
+void DomainReliabilityContext::ResourceState::CommitUpload() {
+  BeaconIterator begin = beacons.begin();
+  BeaconIterator end = begin + uploading_beacons_size;
+  beacons.erase(begin, end);
+  successful_requests -= uploading_successful_requests;
+  failed_requests -= uploading_failed_requests;
+}
+
+bool DomainReliabilityContext::ResourceState::GetOldestBeaconStart(
+    base::TimeTicks* oldest_start_out) const {
+  if (beacons.empty())
+    return false;
+
+  *oldest_start_out = beacons[0].start_time;
+  return true;
+}
+
+void DomainReliabilityContext::ResourceState::RemoveOldestBeacon() {
+  DCHECK(!beacons.empty());
+  beacons.erase(beacons.begin());
+  // If that just removed a beacon counted in uploading_beacons_size, decrement
+  // that.
+  if (uploading_beacons_size > 0)
+    --uploading_beacons_size;
+}
+
+void DomainReliabilityContext::InitializeResourceStates() {
+  ScopedVector<DomainReliabilityConfig::Resource>::const_iterator it;
+  for (it = config_->resources.begin(); it != config_->resources.end(); ++it)
+    states_.push_back(new ResourceState(this, *it));
+}
+
+scoped_ptr<const Value> DomainReliabilityContext::CreateReport(
+    base::TimeTicks upload_time) const {
+  ListValue* resources_value = new ListValue();
+  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
+    resources_value->Append((*it)->ToValue(upload_time).release());
+
+  DictionaryValue* report_value = new DictionaryValue();
+  report_value->SetString("reporter", kReporter);
+  report_value->Set("resource_reports", resources_value);
+
+  return scoped_ptr<const Value>(report_value);
+}
+
+void DomainReliabilityContext::MarkUpload() {
+  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
+    (*it)->MarkUpload();
+  uploading_beacon_count_ = beacon_count_;
+}
+
+void DomainReliabilityContext::CommitUpload() {
+  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it)
+    (*it)->CommitUpload();
+  beacon_count_ -= uploading_beacon_count_;
+}
+
+void DomainReliabilityContext::RemoveOldestBeacon() {
+  DCHECK_LT(0, beacon_count_);
+
+  base::TimeTicks min_time;
+  ResourceState* min_resource = NULL;
+  for (ResourceStateIterator it = states_.begin(); it != states_.end(); ++it) {
+    base::TimeTicks oldest;
+    if ((*it)->GetOldestBeaconStart(&oldest)) {
+      if (!min_resource || oldest < min_time) {
+        min_time = oldest;
+        min_resource = *it;
+      }
+    }
+  }
+  DCHECK(min_resource);
+
+  VLOG(1) << "Removing oldest beacon from " << min_resource->config->name;
+
+  min_resource->RemoveOldestBeacon();
+  --beacon_count_;
+  // If that just removed a beacon counted in uploading_beacon_count_, decrement
+  // that.
+  if (uploading_beacon_count_ > 0)
+    --uploading_beacon_count_;
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/context.h b/components/domain_reliability/context.h
new file mode 100644
index 0000000..bb6099d
--- /dev/null
+++ b/components/domain_reliability/context.h
@@ -0,0 +1,122 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_CONTEXT_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_CONTEXT_H_
+
+#include <deque>
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "components/domain_reliability/beacon.h"
+#include "components/domain_reliability/config.h"
+#include "components/domain_reliability/domain_reliability_export.h"
+#include "components/domain_reliability/util.h"
+
+class GURL;
+class MockableTime;
+
+namespace domain_reliability {
+
+// The per-domain context for the Domain Reliability client; includes the
+// domain's config and per-resource beacon queues.
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityContext {
+ public:
+  DomainReliabilityContext(
+      MockableTime* time,
+      scoped_ptr<const DomainReliabilityConfig> config);
+  virtual ~DomainReliabilityContext();
+
+  void AddBeacon(const DomainReliabilityBeacon& beacon, const GURL& url);
+
+  void GetQueuedDataForTesting(
+      int resource_index,
+      std::vector<DomainReliabilityBeacon>* beacons_out,
+      int* successful_requests_out,
+      int* failed_requests_out) const;
+
+  const DomainReliabilityConfig& config() { return *config_.get(); }
+
+  // Maximum number of beacons queued per context; if more than this many are
+  // queued; the oldest beacons will be removed.
+  static const int kMaxQueuedBeacons;
+
+ private:
+  // Resource-specific state (queued beacons and request counts).
+  class ResourceState {
+   public:
+    ResourceState(DomainReliabilityContext* context,
+                  const DomainReliabilityConfig::Resource* config);
+    ~ResourceState();
+
+    scoped_ptr<base::Value> ToValue(base::TimeTicks upload_time) const;
+
+    // Remembers the current state of the resource data when an upload starts.
+    void MarkUpload();
+
+    // Uses the state remembered by |MarkUpload| to remove successfully uploaded
+    // data but keep beacons and request counts added after the upload started.
+    void CommitUpload();
+
+    // Gets the start time of the oldest beacon, if there are any. Returns true
+    // and sets |oldest_start_out| if so; otherwise, returns false.
+    bool GetOldestBeaconStart(base::TimeTicks* oldest_start_out) const;
+    // Removes the oldest beacon. DCHECKs if there isn't one.
+    void RemoveOldestBeacon();
+
+    DomainReliabilityContext* context;
+    const DomainReliabilityConfig::Resource* config;
+
+    std::deque<DomainReliabilityBeacon> beacons;
+    int successful_requests;
+    int failed_requests;
+
+    // State saved during uploads; if an upload succeeds, these are used to
+    // remove uploaded data from the beacon list and request counters.
+    size_t uploading_beacons_size;
+    int uploading_successful_requests;
+    int uploading_failed_requests;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(ResourceState);
+  };
+
+  typedef ScopedVector<ResourceState> ResourceStateVector;
+  typedef ResourceStateVector::const_iterator ResourceStateIterator;
+
+  void InitializeResourceStates();
+
+  scoped_ptr<const base::Value> CreateReport(base::TimeTicks upload_time) const;
+
+  // Remembers the current state of the context when an upload starts.
+  void MarkUpload();
+
+  // Uses the state remembered by |MarkUpload| to remove successfully uploaded
+  // data but keep beacons and request counts added after the upload started.
+  void CommitUpload();
+
+  // Finds and removes the oldest beacon. DCHECKs if there is none. (Called
+  // when there are too many beacons queued.)
+  void RemoveOldestBeacon();
+
+  scoped_ptr<const DomainReliabilityConfig> config_;
+  MockableTime* time_;
+
+  // Each ResourceState in |states_| corresponds to the Resource of the same
+  // index in the config.
+  ResourceStateVector states_;
+  int beacon_count_;
+  int uploading_beacon_count_;
+
+  base::WeakPtrFactory<DomainReliabilityContext> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(DomainReliabilityContext);
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_CONTEXT_H_
diff --git a/components/domain_reliability/context_unittest.cc b/components/domain_reliability/context_unittest.cc
new file mode 100644
index 0000000..323f448
--- /dev/null
+++ b/components/domain_reliability/context_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 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 "components/domain_reliability/context.h"
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "components/domain_reliability/test_util.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace domain_reliability {
+
+namespace {
+
+typedef std::vector<DomainReliabilityBeacon> BeaconVector;
+
+DomainReliabilityBeacon MakeBeacon(MockableTime* time) {
+  DomainReliabilityBeacon beacon;
+  beacon.status = "ok";
+  beacon.chrome_error = net::OK;
+  beacon.server_ip = "127.0.0.1";
+  beacon.http_response_code = 200;
+  beacon.elapsed = base::TimeDelta::FromMilliseconds(250);
+  beacon.start_time = time->Now() - beacon.elapsed;
+  return beacon;
+}
+
+}  // namespace
+
+class DomainReliabilityContextTest : public testing::Test {
+ protected:
+  DomainReliabilityContextTest()
+      : context_(&time_,
+                 CreateConfig().Pass()) {}
+
+  bool CheckNoBeacons(int index) {
+    BeaconVector beacons;
+    context_.GetQueuedDataForTesting(index, &beacons, NULL, NULL);
+    return beacons.empty();
+  }
+
+  bool CheckCounts(int index, int expected_successful, int expected_failed) {
+    int successful, failed;
+    context_.GetQueuedDataForTesting(index, NULL, &successful, &failed);
+    return successful == expected_successful && failed == expected_failed;
+  }
+
+  MockTime time_;
+  DomainReliabilityContext context_;
+
+ private:
+  static scoped_ptr<const DomainReliabilityConfig> CreateConfig() {
+    DomainReliabilityConfig* config = new DomainReliabilityConfig();
+    DomainReliabilityConfig::Resource* resource;
+
+    resource = new DomainReliabilityConfig::Resource();
+    resource->name = "always_report";
+    resource->url_patterns.push_back(
+        new std::string("http://example/always_report"));
+    resource->success_sample_rate = 1.0;
+    resource->failure_sample_rate = 1.0;
+    config->resources.push_back(resource);
+
+    resource = new DomainReliabilityConfig::Resource();
+    resource->name = "never_report";
+    resource->url_patterns.push_back(
+        new std::string("http://example/never_report"));
+    resource->success_sample_rate = 0.0;
+    resource->failure_sample_rate = 0.0;
+    config->resources.push_back(resource);
+
+    DomainReliabilityConfig::Collector* collector;
+    collector = new DomainReliabilityConfig::Collector();
+    collector->upload_url = GURL("https://example/upload");
+    config->collectors.push_back(collector);
+
+    return scoped_ptr<const DomainReliabilityConfig>(config);
+  }
+};
+
+TEST_F(DomainReliabilityContextTest, Create) {
+  EXPECT_TRUE(CheckNoBeacons(0));
+  EXPECT_TRUE(CheckCounts(0, 0, 0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+  EXPECT_TRUE(CheckCounts(1, 0, 0));
+}
+
+TEST_F(DomainReliabilityContextTest, NoResource) {
+  DomainReliabilityBeacon beacon = MakeBeacon(&time_);
+  context_.AddBeacon(beacon, GURL("http://example/no_resource"));
+
+  EXPECT_TRUE(CheckNoBeacons(0));
+  EXPECT_TRUE(CheckCounts(0, 0, 0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+  EXPECT_TRUE(CheckCounts(1, 0, 0));
+}
+
+TEST_F(DomainReliabilityContextTest, NeverReport) {
+  DomainReliabilityBeacon beacon = MakeBeacon(&time_);
+  context_.AddBeacon(beacon, GURL("http://example/never_report"));
+
+  EXPECT_TRUE(CheckNoBeacons(0));
+  EXPECT_TRUE(CheckCounts(0, 0, 0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+  EXPECT_TRUE(CheckCounts(1, 1, 0));
+}
+
+TEST_F(DomainReliabilityContextTest, AlwaysReport) {
+  DomainReliabilityBeacon beacon = MakeBeacon(&time_);
+  context_.AddBeacon(beacon, GURL("http://example/always_report"));
+
+  BeaconVector beacons;
+  context_.GetQueuedDataForTesting(0, &beacons, NULL, NULL);
+  EXPECT_EQ(1u, beacons.size());
+  EXPECT_TRUE(CheckCounts(0, 1, 0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+  EXPECT_TRUE(CheckCounts(1, 0, 0));
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/domain_reliability_export.h b/components/domain_reliability/domain_reliability_export.h
new file mode 100644
index 0000000..d0951cc
--- /dev/null
+++ b/components/domain_reliability/domain_reliability_export.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_DOMAIN_RELIABILITY_EXPORT_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_DOMAIN_RELIABILITY_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(DOMAIN_RELIABILITY_IMPLEMENTATION)
+#define DOMAIN_RELIABILITY_EXPORT __declspec(dllexport)
+#else
+#define DOMAIN_RELIABILITY_EXPORT __declspec(dllimport)
+#endif  // defined(DOMAIN_RELIABILITY_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(DOMAIN_RELIABILITY_IMPLEMENTATION)
+#define DOMAIN_RELIABILITY_EXPORT __attribute__((visibility("default")))
+#else
+#define DOMAIN_RELIABILITY_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define DOMAIN_RELIABILITY_EXPORT
+#endif
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_DOMAIN_RELIABILITY_EXPORT_H_
diff --git a/components/domain_reliability/monitor.cc b/components/domain_reliability/monitor.cc
new file mode 100644
index 0000000..09f122fd
--- /dev/null
+++ b/components/domain_reliability/monitor.cc
@@ -0,0 +1,114 @@
+// Copyright 2014 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 "components/domain_reliability/monitor.h"
+
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+
+bool OnIOThread() {
+  return content::BrowserThread::CurrentlyOn(content::BrowserThread::IO);
+}
+
+}  // namespace
+
+namespace domain_reliability {
+
+DomainReliabilityMonitor::DomainReliabilityMonitor()
+    : time_(new ActualTime()) {
+  DCHECK(OnIOThread());
+}
+
+DomainReliabilityMonitor::DomainReliabilityMonitor(
+    scoped_ptr<MockableTime> time)
+    : time_(time.Pass()) {
+  DCHECK(OnIOThread());
+}
+
+DomainReliabilityMonitor::~DomainReliabilityMonitor() {
+  DCHECK(OnIOThread());
+  STLDeleteContainerPairSecondPointers(
+      contexts_.begin(), contexts_.end());
+}
+
+void DomainReliabilityMonitor::OnBeforeRedirect(net::URLRequest* request) {
+  DCHECK(OnIOThread());
+  RequestInfo request_info(*request);
+  // Record the redirect itself in addition to the final request.
+  OnRequestLegComplete(request_info);
+}
+
+void DomainReliabilityMonitor::OnCompleted(net::URLRequest* request,
+                                           bool started) {
+  DCHECK(OnIOThread());
+  if (!started)
+    return;
+  RequestInfo request_info(*request);
+  OnRequestLegComplete(request_info);
+}
+
+DomainReliabilityContext* DomainReliabilityMonitor::AddContextForTesting(
+    scoped_ptr<const DomainReliabilityConfig> config) {
+  DomainReliabilityContext*& context_ref = contexts_[config->domain];
+  DCHECK(!context_ref);
+  context_ref = new DomainReliabilityContext(time_.get(), config.Pass());
+  return context_ref;
+}
+
+DomainReliabilityMonitor::RequestInfo::RequestInfo() {}
+
+DomainReliabilityMonitor::RequestInfo::RequestInfo(
+    const net::URLRequest& request)
+    : url(request.url()),
+      status(request.status()),
+      response_code(-1),
+      socket_address(request.GetSocketAddress()),
+      was_cached(request.was_cached()) {
+  request.GetLoadTimingInfo(&load_timing_info);
+  // Can't get response code of a canceled request -- there's no transaction.
+  if (status.status() != net::URLRequestStatus::CANCELED)
+    response_code = request.GetResponseCode();
+}
+
+DomainReliabilityMonitor::RequestInfo::~RequestInfo() {}
+
+void DomainReliabilityMonitor::OnRequestLegComplete(
+    const RequestInfo& request) {
+  if (request.was_cached ||
+      request.status.status() == net::URLRequestStatus::CANCELED) {
+    return;
+  }
+
+  std::map<std::string, DomainReliabilityContext*>::iterator it =
+      contexts_.find(request.url.host());
+  if (it == contexts_.end())
+    return;
+  DomainReliabilityContext* context = it->second;
+
+  std::string beacon_status;
+  bool got_status = DomainReliabilityUtil::GetBeaconStatus(
+      request.status.error(),
+      request.response_code,
+      &beacon_status);
+  if (!got_status)
+    return;
+
+  DomainReliabilityBeacon beacon;
+  beacon.status = beacon_status;
+  beacon.chrome_error = request.status.error();
+  beacon.server_ip = request.socket_address.host();
+  beacon.http_response_code = request.response_code;
+  beacon.start_time = request.load_timing_info.request_start;
+  beacon.elapsed = time_->Now() - beacon.start_time;
+  context->AddBeacon(beacon, request.url);
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/monitor.h b/components/domain_reliability/monitor.h
new file mode 100644
index 0000000..5d189dc
--- /dev/null
+++ b/components/domain_reliability/monitor.h
@@ -0,0 +1,74 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_MONITOR_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_MONITOR_H_
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "components/domain_reliability/beacon.h"
+#include "components/domain_reliability/config.h"
+#include "components/domain_reliability/context.h"
+#include "components/domain_reliability/domain_reliability_export.h"
+#include "components/domain_reliability/util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_timing_info.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+class URLRequest;
+}
+
+namespace domain_reliability {
+
+// The top-level per-profile object that measures requests and hands off the
+// measurements to the proper |DomainReliabilityContext|.  Referenced by the
+// |ChromeNetworkDelegate|, which calls the On* methods.
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityMonitor {
+ public:
+  // NB: We don't take a URLRequestContextGetter because we already live on the
+  // I/O thread.
+  DomainReliabilityMonitor();
+  explicit DomainReliabilityMonitor(scoped_ptr<MockableTime> time);
+  ~DomainReliabilityMonitor();
+
+  // Should be called from the profile's NetworkDelegate on the corresponding
+  // events:
+  void OnBeforeRedirect(net::URLRequest* request);
+  void OnCompleted(net::URLRequest* request, bool started);
+
+  // Creates a context for testing, adds it to the monitor, and returns a
+  // pointer to it. (The pointer is only valid until the MOnitor is destroyed.)
+  DomainReliabilityContext* AddContextForTesting(
+      scoped_ptr<const DomainReliabilityConfig> config);
+
+ private:
+  friend class DomainReliabilityMonitorTest;
+
+  struct DOMAIN_RELIABILITY_EXPORT RequestInfo {
+    RequestInfo();
+    RequestInfo(const net::URLRequest& request);
+    ~RequestInfo();
+
+    GURL url;
+    net::URLRequestStatus status;
+    int response_code;
+    net::HostPortPair socket_address;
+    net::LoadTimingInfo load_timing_info;
+    bool was_cached;
+  };
+
+  void OnRequestLegComplete(const RequestInfo& info);
+
+  scoped_ptr<MockableTime> time_;
+  std::map<std::string, DomainReliabilityContext*> contexts_;
+
+  DISALLOW_COPY_AND_ASSIGN(DomainReliabilityMonitor);
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_MONITOR_H_
diff --git a/components/domain_reliability/monitor_unittest.cc b/components/domain_reliability/monitor_unittest.cc
new file mode 100644
index 0000000..9d15114
--- /dev/null
+++ b/components/domain_reliability/monitor_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 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 "components/domain_reliability/monitor.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "components/domain_reliability/beacon.h"
+#include "components/domain_reliability/config.h"
+#include "components/domain_reliability/test_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace domain_reliability {
+
+typedef std::vector<DomainReliabilityBeacon> BeaconVector;
+
+class DomainReliabilityMonitorTest : public testing::Test {
+ protected:
+  typedef DomainReliabilityMonitor::RequestInfo RequestInfo;
+
+  DomainReliabilityMonitorTest()
+      : bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+        time_(new MockTime()),
+        monitor_(scoped_ptr<MockableTime>(time_)),
+        context_(monitor_.AddContextForTesting(CreateConfig())) {}
+
+  static scoped_ptr<const DomainReliabilityConfig> CreateConfig() {
+    DomainReliabilityConfig* config = new DomainReliabilityConfig();
+    DomainReliabilityConfig::Resource* resource;
+
+    resource = new DomainReliabilityConfig::Resource();
+    resource->name = "always_report";
+    resource->url_patterns.push_back(
+        new std::string("http://example/always_report"));
+    resource->success_sample_rate = 1.0;
+    resource->failure_sample_rate = 1.0;
+    config->resources.push_back(resource);
+
+    resource = new DomainReliabilityConfig::Resource();
+    resource->name = "never_report";
+    resource->url_patterns.push_back(
+        new std::string("http://example/never_report"));
+    resource->success_sample_rate = 0.0;
+    resource->failure_sample_rate = 0.0;
+    config->resources.push_back(resource);
+
+    DomainReliabilityConfig::Collector* collector;
+    collector = new DomainReliabilityConfig::Collector();
+    collector->upload_url = GURL("https://example/upload");
+    config->domain = "example";
+    config->collectors.push_back(collector);
+
+    return scoped_ptr<const DomainReliabilityConfig>(config);
+  }
+
+  RequestInfo MakeRequestInfo() {
+    RequestInfo request;
+    request.status = net::URLRequestStatus();
+    request.response_code = 200;
+    request.was_cached = false;
+    return request;
+  }
+
+  bool CheckNoBeacons(int index) {
+    BeaconVector beacons;
+    int successful, failed;
+    context_->GetQueuedDataForTesting(index, &beacons, &successful, &failed);
+    return beacons.empty() && successful == 0 && failed == 0;
+  }
+
+  void OnRequestLegComplete(const RequestInfo& info) {
+    monitor_.OnRequestLegComplete(info);
+  }
+
+  content::TestBrowserThreadBundle bundle_;
+  MockTime* time_;
+  DomainReliabilityMonitor monitor_;
+  DomainReliabilityContext* context_;
+  DomainReliabilityMonitor::RequestInfo request_;
+};
+
+TEST_F(DomainReliabilityMonitorTest, Create) {
+  EXPECT_TRUE(CheckNoBeacons(0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+}
+
+TEST_F(DomainReliabilityMonitorTest, NoContextRequest) {
+  RequestInfo request = MakeRequestInfo();
+  request.url = GURL("http://no-context/");
+  OnRequestLegComplete(request);
+
+  EXPECT_TRUE(CheckNoBeacons(0));
+  EXPECT_TRUE(CheckNoBeacons(1));
+}
+
+TEST_F(DomainReliabilityMonitorTest, ContextRequest) {
+  RequestInfo request = MakeRequestInfo();
+  request.url = GURL("http://example/always_report");
+  OnRequestLegComplete(request);
+
+  BeaconVector beacons;
+  context_->GetQueuedDataForTesting(0, &beacons, NULL, NULL);
+  EXPECT_EQ(1u, beacons.size());
+  EXPECT_TRUE(CheckNoBeacons(1));
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/test_util.cc b/components/domain_reliability/test_util.cc
new file mode 100644
index 0000000..08ddd04
--- /dev/null
+++ b/components/domain_reliability/test_util.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 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 "components/domain_reliability/test_util.h"
+
+#include "base/bind.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace domain_reliability {
+
+namespace {
+
+class MockTimer : public MockableTime::Timer {
+ public:
+  MockTimer(MockTime* time)
+      : time_(time),
+        running_(false),
+        callback_sequence_number_(0),
+        weak_factory_(this) {
+    DCHECK(time);
+  }
+  virtual ~MockTimer() {}
+
+  virtual void Start(const tracked_objects::Location& posted_from,
+                     base::TimeDelta delay,
+                     const base::Closure& user_task) OVERRIDE {
+    DCHECK(!user_task.is_null());
+
+    if (running_)
+      ++callback_sequence_number_;
+    running_ = true;
+    user_task_ = user_task;
+    time_->AddTask(delay,
+        base::Bind(&MockTimer::OnDelayPassed,
+                   weak_factory_.GetWeakPtr(),
+                   callback_sequence_number_));
+  }
+
+  virtual void Stop() OVERRIDE {
+    if (running_) {
+      ++callback_sequence_number_;
+      running_ = false;
+    }
+  }
+
+  virtual bool IsRunning() OVERRIDE { return running_; }
+
+ private:
+  void OnDelayPassed(int expected_callback_sequence_number) {
+    if (callback_sequence_number_ != expected_callback_sequence_number)
+      return;
+
+    DCHECK(running_);
+    running_ = false;
+
+    // Grab user task in case it re-entrantly starts the timer again.
+    base::Closure task_to_run = user_task_;
+    user_task_.Reset();
+    task_to_run.Run();
+  }
+
+  MockTime* time_;
+  bool running_;
+  int callback_sequence_number_;
+  base::Closure user_task_;
+  base::WeakPtrFactory<MockTimer> weak_factory_;
+};
+
+}  // namespace
+
+TestCallback::TestCallback()
+    : callback_(base::Bind(&TestCallback::OnCalled,
+                           base::Unretained(this))),
+      called_(false) {}
+
+TestCallback::~TestCallback() {}
+
+void TestCallback::OnCalled() {
+  EXPECT_FALSE(called_);
+  called_ = true;
+}
+
+MockTime::MockTime()
+    : now_(base::TimeTicks::Now()),
+      epoch_(now_),
+      task_sequence_number_(0) {
+  VLOG(1) << "Creating mock time: T=" << elapsed_sec() << "s";
+}
+
+MockTime::~MockTime() {}
+
+base::TimeTicks MockTime::Now() { return now_; }
+
+scoped_ptr<MockableTime::Timer> MockTime::CreateTimer() {
+  return scoped_ptr<MockableTime::Timer>(new MockTimer(this));
+}
+
+void MockTime::Advance(base::TimeDelta delta) {
+  base::TimeTicks target = now_ + delta;
+
+  while (!tasks_.empty() && tasks_.begin()->first.time <= target) {
+    TaskKey key = tasks_.begin()->first;
+    base::Closure task = tasks_.begin()->second;
+    tasks_.erase(tasks_.begin());
+
+    DCHECK(now_ <= key.time);
+    DCHECK(key.time <= target);
+    now_ = key.time;
+    VLOG(1) << "Advancing mock time: task at T=" << elapsed_sec() << "s";
+
+    task.Run();
+  }
+
+  DCHECK(now_ <= target);
+  now_ = target;
+  VLOG(1) << "Advanced mock time: T=" << elapsed_sec() << "s";
+}
+
+void MockTime::AddTask(base::TimeDelta delay, const base::Closure& task) {
+  tasks_[TaskKey(now_ + delay, task_sequence_number_++)] = task;
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/test_util.h b/components/domain_reliability/test_util.h
new file mode 100644
index 0000000..c7a1f2d50
--- /dev/null
+++ b/components/domain_reliability/test_util.h
@@ -0,0 +1,90 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_TEST_UTIL_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_TEST_UTIL_H_
+
+#include "base/callback.h"
+#include "components/domain_reliability/monitor.h"
+#include "components/domain_reliability/util.h"
+#include "net/base/host_port_pair.h"
+
+namespace net {
+class URLRequestStatus;
+}  // namespace net
+
+namespace domain_reliability {
+
+// A simple test callback that remembers whether it's been called.
+class TestCallback {
+ public:
+  TestCallback();
+  ~TestCallback();
+
+  // Returns a callback that can be called only once.
+  const base::Closure& callback() const { return callback_; }
+  // Returns whether the callback returned by |callback()| has been called.
+  bool called() const { return called_; }
+
+ private:
+  void OnCalled();
+
+  base::Closure callback_;
+  bool called_;
+};
+
+class MockTime : public MockableTime {
+ public:
+  MockTime();
+  // N.B.: Tasks (and therefore Timers) scheduled to run in the future will
+  // never be run if MockTime is destroyed before the mock time is advanced
+  // to their scheduled time.
+  virtual ~MockTime();
+
+  virtual base::TimeTicks Now() OVERRIDE;
+  virtual scoped_ptr<MockableTime::Timer> CreateTimer() OVERRIDE;
+
+  // Pretends that |delta| has passed, and runs tasks that would've happened
+  // during that interval (with |Now()| returning proper values while they
+  // execute!)
+  void Advance(base::TimeDelta delta);
+
+  // Queues |task| to be run after |delay|. (Lighter-weight than mocking an
+  // entire message pump.)
+  void AddTask(base::TimeDelta delay, const base::Closure& task);
+
+ private:
+  // Key used to store tasks in the task map. Includes the time the task should
+  // run and a sequence number to disambiguate tasks with the same time.
+  struct TaskKey {
+    TaskKey(base::TimeTicks time, int sequence_number)
+        : time(time),
+          sequence_number(sequence_number) {}
+
+    base::TimeTicks time;
+    int sequence_number;
+  };
+
+  // Comparator for TaskKey; sorts by time, then by sequence number.
+  struct TaskKeyCompare {
+    bool operator() (const TaskKey& lhs, const TaskKey& rhs) const {
+      return lhs.time < rhs.time ||
+             (lhs.time == rhs.time &&
+              lhs.sequence_number < rhs.sequence_number);
+    }
+  };
+
+  typedef std::map<TaskKey, base::Closure, TaskKeyCompare> TaskMap;
+
+  int elapsed_sec() { return (now_ - epoch_).InSeconds(); }
+
+  base::TimeTicks now_;
+  base::TimeTicks epoch_;
+  int task_sequence_number_;
+  TaskMap tasks_;
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_TEST_UTIL_H_
diff --git a/components/domain_reliability/util.cc b/components/domain_reliability/util.cc
new file mode 100644
index 0000000..75164d53
--- /dev/null
+++ b/components/domain_reliability/util.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 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 "components/domain_reliability/util.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+
+namespace domain_reliability {
+
+namespace {
+
+class ActualTimer : public MockableTime::Timer {
+ public:
+  // Initialize base timer with retain_user_info and is_repeating false.
+  ActualTimer() : base_timer_(false, false) {}
+  virtual ~ActualTimer() {}
+
+  virtual void Start(const tracked_objects::Location& posted_from,
+                     base::TimeDelta delay,
+                     const base::Closure& user_task) OVERRIDE {
+    base_timer_.Start(posted_from, delay, user_task);
+  }
+
+  virtual void Stop() OVERRIDE {
+    base_timer_.Stop();
+  }
+
+  virtual bool IsRunning() OVERRIDE {
+    return base_timer_.IsRunning();
+  }
+
+ private:
+  base::Timer base_timer_;
+};
+
+const struct NetErrorMapping {
+  int net_error;
+  const char* beacon_status;
+} net_error_map[] = {
+  { net::OK, "ok" },
+  { net::ERR_TIMED_OUT, "tcp.connection.timed_out" },
+  { net::ERR_CONNECTION_CLOSED, "tcp.connection.closed" },
+  { net::ERR_CONNECTION_RESET, "tcp.connection.reset" },
+  { net::ERR_CONNECTION_REFUSED, "tcp.connection.refused" },
+  { net::ERR_CONNECTION_ABORTED, "tcp.connection.aborted" },
+  { net::ERR_CONNECTION_FAILED, "tcp.connection.failed" },
+  { net::ERR_NAME_NOT_RESOLVED, "dns" },
+  { net::ERR_SSL_PROTOCOL_ERROR, "ssl.protocol.error" },
+  { net::ERR_ADDRESS_INVALID, "tcp.connection.address_invalid" },
+  { net::ERR_ADDRESS_UNREACHABLE, "tcp.connection.address_unreachable" },
+  { net::ERR_CONNECTION_TIMED_OUT, "tcp.connection.timed_out" },
+  { net::ERR_NAME_RESOLUTION_FAILED, "dns" },
+  { net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN,
+        "ssl.pinned_key_not_in_cert_chain" },
+  { net::ERR_CERT_COMMON_NAME_INVALID, "ssl.cert.name_invalid" },
+  { net::ERR_CERT_DATE_INVALID, "ssl.cert.date_invalid" },
+  { net::ERR_CERT_AUTHORITY_INVALID, "ssl.cert.authority_invalid" },
+  { net::ERR_CERT_REVOKED, "ssl.cert.revoked" },
+  { net::ERR_CERT_INVALID, "ssl.cert.invalid" },
+  { net::ERR_EMPTY_RESPONSE, "http.empty_response" },
+  { net::ERR_SPDY_PING_FAILED, "spdy.ping_failed" },
+  { net::ERR_SPDY_PROTOCOL_ERROR, "spdy.protocol" },
+  { net::ERR_QUIC_PROTOCOL_ERROR, "quic.protocol" },
+  { net::ERR_DNS_MALFORMED_RESPONSE, "dns.protocol" },
+  { net::ERR_DNS_SERVER_FAILED, "dns.server" },
+  { net::ERR_DNS_TIMED_OUT, "dns.timed_out" },
+};
+
+}  // namespace
+
+// static
+bool DomainReliabilityUtil::GetBeaconStatus(
+    int net_error,
+    int http_response_code,
+    std::string* beacon_status_out) {
+  if (net_error == net::OK) {
+    if (http_response_code >= 400 && http_response_code < 600)
+      *beacon_status_out = base::StringPrintf("http.%d", http_response_code);
+    else
+      *beacon_status_out = "ok";
+    return true;
+  } else {
+    for (size_t i = 0; i < arraysize(net_error_map); i++) {
+      if (net_error_map[i].net_error == net_error) {
+        *beacon_status_out = net_error_map[i].beacon_status;
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+MockableTime::Timer::~Timer() {}
+
+MockableTime::Timer::Timer() {}
+
+MockableTime::~MockableTime() {}
+
+MockableTime::MockableTime() {}
+
+ActualTime::ActualTime() {}
+
+ActualTime::~ActualTime() {}
+
+base::TimeTicks ActualTime::Now() { return base::TimeTicks::Now(); }
+
+scoped_ptr<MockableTime::Timer> ActualTime::CreateTimer() {
+  return scoped_ptr<MockableTime::Timer>(new ActualTimer());
+}
+
+}  // namespace domain_reliability
diff --git a/components/domain_reliability/util.h b/components/domain_reliability/util.h
new file mode 100644
index 0000000..2603d8d
--- /dev/null
+++ b/components/domain_reliability/util.h
@@ -0,0 +1,74 @@
+// Copyright 2014 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 COMPONENTS_DOMAIN_RELIABILITY_UTIL_H_
+#define COMPONENTS_DOMAIN_RELIABILITY_UTIL_H_
+
+#include <map>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "components/domain_reliability/domain_reliability_export.h"
+
+namespace domain_reliability {
+
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityUtil {
+ public:
+  // Attempts to convert a net error and an HTTP response code into the status
+  // string that should be recorded in a beacon. Returns true if it could.
+  static bool GetBeaconStatus(
+      int net_error,
+      int http_response_code,
+      std::string* beacon_status_out);
+};
+
+// Mockable wrapper around TimeTicks::Now and Timer. Mock version is in
+// test_util.h.
+class DOMAIN_RELIABILITY_EXPORT MockableTime {
+ public:
+  // Mockable wrapper around (a subset of) base::Timer.
+  class DOMAIN_RELIABILITY_EXPORT Timer {
+   public:
+    virtual ~Timer();
+
+    virtual void Start(const tracked_objects::Location& posted_from,
+                       base::TimeDelta delay,
+                       const base::Closure& user_task) = 0;
+    virtual void Stop() = 0;
+    virtual bool IsRunning() = 0;
+
+   protected:
+    Timer();
+  };
+
+  virtual ~MockableTime();
+
+  // Returns base::TimeTicks::Now() or a mocked version thereof.
+  virtual base::TimeTicks Now() = 0;
+  // Returns a new Timer, or a mocked version thereof.
+  virtual scoped_ptr<MockableTime::Timer> CreateTimer() = 0;
+
+ protected:
+  MockableTime();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockableTime);
+};
+
+// Implementation of MockableTime that passes through to base::TimeTicks::Now()
+// and base::Timer.
+class DOMAIN_RELIABILITY_EXPORT ActualTime : public MockableTime {
+ public:
+  ActualTime();
+  virtual ~ActualTime();
+
+  virtual base::TimeTicks Now() OVERRIDE;
+  virtual scoped_ptr<MockableTime::Timer> CreateTimer() OVERRIDE;
+};
+
+}  // namespace domain_reliability
+
+#endif  // COMPONENTS_DOMAIN_RELIABILITY_UTIL_H_
diff --git a/components/domain_reliability/util_unittest.cc b/components/domain_reliability/util_unittest.cc
new file mode 100644
index 0000000..3146eda
--- /dev/null
+++ b/components/domain_reliability/util_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 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 "components/domain_reliability/util.h"
+
+#include "base/bind.h"
+#include "components/domain_reliability/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace domain_reliability {
+
+class DomainReliabilityMockTimeTest : public testing::Test {
+ protected:
+  MockTime time_;
+};
+
+TEST_F(DomainReliabilityMockTimeTest, Null) {
+}
+
+TEST_F(DomainReliabilityMockTimeTest, NowAndAdvance) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+
+  TimeTicks initial = time_.Now();
+  time_.Advance(delta);
+  TimeTicks final = time_.Now();
+  EXPECT_EQ(delta, final - initial);
+}
+
+TEST_F(DomainReliabilityMockTimeTest, AddTask) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  TestCallback callback;
+
+  time_.AddTask(2 * delta, callback.callback());
+  time_.Advance(delta);
+  EXPECT_FALSE(callback.called());
+  time_.Advance(delta);
+  EXPECT_TRUE(callback.called());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerCreate) {
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerIsRunning) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  TestCallback callback;
+
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+  EXPECT_FALSE(timer->IsRunning());
+  timer->Start(FROM_HERE, delta, callback.callback());
+  EXPECT_TRUE(timer->IsRunning());
+  timer->Stop();
+  EXPECT_FALSE(timer->IsRunning());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerGoesOff) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  TestCallback callback;
+
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+
+  timer->Start(FROM_HERE, 2 * delta, callback.callback());
+  time_.Advance(delta);
+  EXPECT_FALSE(callback.called());
+  time_.Advance(delta);
+  EXPECT_TRUE(callback.called());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerStopped) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  TestCallback callback;
+
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+
+  timer->Start(FROM_HERE, 2 * delta, callback.callback());
+  time_.Advance(delta);
+  timer->Stop();
+  time_.Advance(delta);
+  EXPECT_FALSE(callback.called());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerRestarted) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  TestCallback callback;
+
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+
+  timer->Start(FROM_HERE, 2 * delta, callback.callback());
+  time_.Advance(delta);
+  timer->Start(FROM_HERE, 2 * delta, callback.callback());
+  time_.Advance(delta);
+  EXPECT_FALSE(callback.called());
+  time_.Advance(delta);
+  EXPECT_TRUE(callback.called());
+}
+
+TEST_F(DomainReliabilityMockTimeTest, TimerReentrantStart) {
+  const TimeDelta delta = TimeDelta::FromSeconds(1);
+  scoped_ptr<MockTime::Timer> timer(time_.CreateTimer());
+  TestCallback callback;
+
+  timer->Start(
+      FROM_HERE,
+      delta,
+      base::Bind(
+          &MockTime::Timer::Start,
+          base::Unretained(timer.get()),
+          FROM_HERE,
+          delta,
+          callback.callback()));
+  time_.Advance(delta);
+  EXPECT_FALSE(callback.called());
+  EXPECT_TRUE(timer->IsRunning());
+  time_.Advance(delta);
+  EXPECT_TRUE(callback.called());
+  EXPECT_FALSE(timer->IsRunning());
+}
+
+}  // namespace domain_reliability