Upload UMA data using protocol buffers.

For now, we will also preserve the existing XML upload system, so that there is no risk of data loss.

Previously committed as [ https://chromiumcodereview.appspot.com/9232071/ ].  This fixes the ChromiumOS compile.

BUG=109817
TEST=unit tested
[email protected]

Review URL: https://chromiumcodereview.appspot.com/9474041

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124086 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/metrics/metrics_log.cc b/chrome/browser/metrics/metrics_log.cc
index 7170b03..1f10db6a 100644
--- a/chrome/browser/metrics/metrics_log.cc
+++ b/chrome/browser/metrics/metrics_log.cc
@@ -12,6 +12,7 @@
 #include "base/lazy_instance.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/perftimer.h"
+#include "base/string_number_conversions.h"
 #include "base/string_util.h"
 #include "base/sys_info.h"
 #include "base/third_party/nspr/prtime.h"
@@ -20,14 +21,19 @@
 #include "chrome/browser/autocomplete/autocomplete.h"
 #include "chrome/browser/autocomplete/autocomplete_match.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/gpu_performance_stats.h"
 #include "chrome/browser/plugin_prefs.h"
 #include "chrome/browser/prefs/pref_service.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_version_info.h"
 #include "chrome/common/logging_chrome.h"
+#include "chrome/common/metrics/proto/omnibox_event.pb.h"
+#include "chrome/common/metrics/proto/system_profile.pb.h"
 #include "chrome/common/pref_names.h"
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/gpu_data_manager.h"
 #include "content/public/common/gpu_info.h"
+#include "content/public/common/content_client.h"
 #include "googleurl/src/gurl.h"
 #include "ui/gfx/screen.h"
 #include "webkit/plugins/webplugininfo.h"
@@ -40,6 +46,8 @@
 #endif
 
 using content::GpuDataManager;
+using metrics::OmniboxEventProto;
+using metrics::SystemProfileProto;
 
 namespace {
 
@@ -55,11 +63,96 @@
   }
 }
 
+OmniboxEventProto::InputType AsOmniboxEventInputType(
+    AutocompleteInput::Type type) {
+  switch (type) {
+    case AutocompleteInput::INVALID:
+      return OmniboxEventProto::INVALID;
+    case AutocompleteInput::UNKNOWN:
+      return OmniboxEventProto::UNKNOWN;
+    case AutocompleteInput::REQUESTED_URL:
+      return OmniboxEventProto::REQUESTED_URL;
+    case AutocompleteInput::URL:
+      return OmniboxEventProto::URL;
+    case AutocompleteInput::QUERY:
+      return OmniboxEventProto::QUERY;
+    case AutocompleteInput::FORCED_QUERY:
+      return OmniboxEventProto::FORCED_QUERY;
+    default:
+      NOTREACHED();
+      return OmniboxEventProto::INVALID;
+  }
+}
+
+OmniboxEventProto::Suggestion::ProviderType AsOmniboxEventProviderType(
+    const AutocompleteProvider* provider) {
+  if (!provider)
+    return OmniboxEventProto::Suggestion::UNKNOWN_PROVIDER;
+
+  const std::string& name = provider->name();
+  if (name == "HistoryURL")
+    return OmniboxEventProto::Suggestion::URL;
+  if (name == "HistoryContents")
+    return OmniboxEventProto::Suggestion::HISTORY_CONTENTS;
+  if (name == "HistoryQuickProvider")
+    return OmniboxEventProto::Suggestion::HISTORY_QUICK;
+  if (name == "Search")
+    return OmniboxEventProto::Suggestion::SEARCH;
+  if (name == "Keyword")
+    return OmniboxEventProto::Suggestion::KEYWORD;
+  if (name == "Builtin")
+    return OmniboxEventProto::Suggestion::BUILTIN;
+  if (name == "ShortcutsProvider")
+    return OmniboxEventProto::Suggestion::SHORTCUTS;
+  if (name == "ExtensionApps")
+    return OmniboxEventProto::Suggestion::EXTENSION_APPS;
+
+  NOTREACHED();
+  return OmniboxEventProto::Suggestion::UNKNOWN_PROVIDER;
+}
+
+OmniboxEventProto::Suggestion::ResultType AsOmniboxEventResultType(
+    AutocompleteMatch::Type type) {
+  switch (type) {
+    case AutocompleteMatch::URL_WHAT_YOU_TYPED:
+      return OmniboxEventProto::Suggestion::URL_WHAT_YOU_TYPED;
+    case AutocompleteMatch::HISTORY_URL:
+      return OmniboxEventProto::Suggestion::HISTORY_URL;
+    case AutocompleteMatch::HISTORY_TITLE:
+      return OmniboxEventProto::Suggestion::HISTORY_TITLE;
+    case AutocompleteMatch::HISTORY_BODY:
+      return OmniboxEventProto::Suggestion::HISTORY_BODY;
+    case AutocompleteMatch::HISTORY_KEYWORD:
+      return OmniboxEventProto::Suggestion::HISTORY_KEYWORD;
+    case AutocompleteMatch::NAVSUGGEST:
+      return OmniboxEventProto::Suggestion::NAVSUGGEST;
+    case AutocompleteMatch::SEARCH_WHAT_YOU_TYPED:
+      return OmniboxEventProto::Suggestion::SEARCH_WHAT_YOU_TYPED;
+    case AutocompleteMatch::SEARCH_HISTORY:
+      return OmniboxEventProto::Suggestion::SEARCH_HISTORY;
+    case AutocompleteMatch::SEARCH_SUGGEST:
+      return OmniboxEventProto::Suggestion::SEARCH_SUGGEST;
+    case AutocompleteMatch::SEARCH_OTHER_ENGINE:
+      return OmniboxEventProto::Suggestion::SEARCH_OTHER_ENGINE;
+    case AutocompleteMatch::EXTENSION_APP:
+      return OmniboxEventProto::Suggestion::EXTENSION_APP;
+    default:
+      NOTREACHED();
+      return OmniboxEventProto::Suggestion::UNKNOWN_RESULT_TYPE;
+  }
+}
+
 // Returns the plugin preferences corresponding for this user, if available.
 // If multiple user profiles are loaded, returns the preferences corresponding
 // to an arbitrary one of the profiles.
 PluginPrefs* GetPluginPrefs() {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
+
+  if (!profile_manager) {
+    // The profile manager can be NULL when testing.
+    return NULL;
+  }
+
   std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
   if (profiles.empty())
     return NULL;
@@ -67,6 +160,17 @@
   return PluginPrefs::GetForProfile(profiles.front());
 }
 
+// Fills |plugin| with the info contained in |plugin_info| and |plugin_prefs|.
+void SetPluginInfo(const webkit::WebPluginInfo& plugin_info,
+                   const PluginPrefs* plugin_prefs,
+                   SystemProfileProto::Plugin* plugin) {
+  plugin->set_name(UTF16ToUTF8(plugin_info.name));
+  plugin->set_filename(plugin_info.path.BaseName().AsUTF8Unsafe());
+  plugin->set_version(UTF16ToUTF8(plugin_info.version));
+  if (plugin_prefs)
+    plugin->set_is_disabled(!plugin_prefs->IsPluginEnabled(plugin_info));
+}
+
 }  // namespace
 
 static base::LazyInstance<std::string>::Leaky
@@ -124,7 +228,8 @@
   return g_version_extension.Get();
 }
 
-void MetricsLog::RecordIncrementalStabilityElements() {
+void MetricsLog::RecordIncrementalStabilityElements(
+    const std::vector<webkit::WebPluginInfo>& plugin_list) {
   DCHECK(!locked_);
 
   PrefService* pref = g_browser_process->local_state();
@@ -140,15 +245,15 @@
     WriteRequiredStabilityAttributes(pref);
     WriteRealtimeStabilityAttributes(pref);
 
-    WritePluginStabilityElements(pref);
+    WritePluginStabilityElements(plugin_list, pref);
   }
 }
 
-void MetricsLog::WriteStabilityElement(PrefService* pref) {
+void MetricsLog::WriteStabilityElement(
+    const std::vector<webkit::WebPluginInfo>& plugin_list,
+    PrefService* pref) {
   DCHECK(!locked_);
 
-  DCHECK(pref);
-
   // Get stability attributes out of Local State, zeroing out stored values.
   // NOTE: This could lead to some data loss if this report isn't successfully
   //       sent, but that's true for all the metrics.
@@ -157,31 +262,51 @@
   WriteRequiredStabilityAttributes(pref);
   WriteRealtimeStabilityAttributes(pref);
 
-  // TODO(jar): The following are all optional, so we *could* optimize them for
-  // values of zero (and not include them).
-  WriteIntAttribute("incompleteshutdowncount",
-                    pref->GetInteger(
-                        prefs::kStabilityIncompleteSessionEndCount));
+  int incomplete_shutdown_count =
+      pref->GetInteger(prefs::kStabilityIncompleteSessionEndCount);
   pref->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
-
-
-  WriteIntAttribute("breakpadregistrationok",
-      pref->GetInteger(prefs::kStabilityBreakpadRegistrationSuccess));
+  int breakpad_registration_success_count =
+      pref->GetInteger(prefs::kStabilityBreakpadRegistrationSuccess);
   pref->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0);
-  WriteIntAttribute("breakpadregistrationfail",
-      pref->GetInteger(prefs::kStabilityBreakpadRegistrationFail));
+  int breakpad_registration_failure_count =
+      pref->GetInteger(prefs::kStabilityBreakpadRegistrationFail);
   pref->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0);
-  WriteIntAttribute("debuggerpresent",
-                   pref->GetInteger(prefs::kStabilityDebuggerPresent));
+  int debugger_present_count =
+      pref->GetInteger(prefs::kStabilityDebuggerPresent);
   pref->SetInteger(prefs::kStabilityDebuggerPresent, 0);
-  WriteIntAttribute("debuggernotpresent",
-                   pref->GetInteger(prefs::kStabilityDebuggerNotPresent));
+  int debugger_not_present_count =
+      pref->GetInteger(prefs::kStabilityDebuggerNotPresent);
   pref->SetInteger(prefs::kStabilityDebuggerNotPresent, 0);
 
-  WritePluginStabilityElements(pref);
+  // TODO(jar): The following are all optional, so we *could* optimize them for
+  // values of zero (and not include them).
+
+  // Write the XML version.
+  WriteIntAttribute("incompleteshutdowncount", incomplete_shutdown_count);
+  WriteIntAttribute("breakpadregistrationok",
+                    breakpad_registration_success_count);
+  WriteIntAttribute("breakpadregistrationfail",
+                    breakpad_registration_failure_count);
+  WriteIntAttribute("debuggerpresent", debugger_present_count);
+  WriteIntAttribute("debuggernotpresent", debugger_not_present_count);
+
+  // Write the protobuf version.
+  SystemProfileProto::Stability* stability =
+      uma_proto_.mutable_system_profile()->mutable_stability();
+  stability->set_incomplete_shutdown_count(incomplete_shutdown_count);
+  stability->set_breakpad_registration_success_count(
+      breakpad_registration_success_count);
+  stability->set_breakpad_registration_failure_count(
+      breakpad_registration_failure_count);
+  stability->set_debugger_present_count(debugger_present_count);
+  stability->set_debugger_not_present_count(debugger_not_present_count);
+
+  WritePluginStabilityElements(plugin_list, pref);
 }
 
-void MetricsLog::WritePluginStabilityElements(PrefService* pref) {
+void MetricsLog::WritePluginStabilityElements(
+    const std::vector<webkit::WebPluginInfo>& plugin_list,
+    PrefService* pref) {
   // Now log plugin stability info.
   const ListValue* plugin_stats_list = pref->GetList(
       prefs::kStabilityPluginStats);
@@ -189,6 +314,9 @@
     return;
 
   OPEN_ELEMENT_FOR_SCOPE("plugins");
+  SystemProfileProto::Stability* stability =
+      uma_proto_.mutable_system_profile()->mutable_stability();
+  PluginPrefs* plugin_prefs = GetPluginPrefs();
   for (ListValue::const_iterator iter = plugin_stats_list->begin();
        iter != plugin_stats_list->end(); ++iter) {
     if (!(*iter)->IsType(Value::TYPE_DICTIONARY)) {
@@ -200,10 +328,15 @@
     std::string plugin_name;
     plugin_dict->GetString(prefs::kStabilityPluginName, &plugin_name);
 
+    std::string base64_name_hash;
+    uint64 numeric_name_hash_ignored;
+    CreateHashes(plugin_name, &base64_name_hash, &numeric_name_hash_ignored);
+
+    // Write the XML verison.
     OPEN_ELEMENT_FOR_SCOPE("pluginstability");
     // Use "filename" instead of "name", otherwise we need to update the
     // UMA servers.
-    WriteAttribute("filename", CreateBase64Hash(plugin_name));
+    WriteAttribute("filename", base64_name_hash);
 
     int launches = 0;
     plugin_dict->GetInteger(prefs::kStabilityPluginLaunches, &launches);
@@ -216,20 +349,60 @@
     int crashes = 0;
     plugin_dict->GetInteger(prefs::kStabilityPluginCrashes, &crashes);
     WriteIntAttribute("crashcount", crashes);
+
+    // Write the protobuf version.
+    // Note that this search is potentially a quadratic operation, but given the
+    // low number of plugins installed on a "reasonable" setup, this should be
+    // fine.
+    // TODO(isherman): Verify that this does not show up as a hotspot in
+    // profiler runs.
+    const webkit::WebPluginInfo* plugin_info = NULL;
+    const string16 plugin_name_utf16 = UTF8ToUTF16(plugin_name);
+    for (std::vector<webkit::WebPluginInfo>::const_iterator iter =
+             plugin_list.begin();
+         iter != plugin_list.end(); ++iter) {
+      if (iter->name == plugin_name_utf16) {
+        plugin_info = &(*iter);
+        break;
+      }
+    }
+
+    if (!plugin_info) {
+      NOTREACHED();
+      continue;
+    }
+
+    SystemProfileProto::Stability::PluginStability* plugin_stability =
+        stability->add_plugin_stability();
+    SetPluginInfo(*plugin_info, plugin_prefs,
+                  plugin_stability->mutable_plugin());
+    plugin_stability->set_launch_count(launches);
+    plugin_stability->set_instance_count(instances);
+    plugin_stability->set_crash_count(crashes);
   }
 
   pref->ClearPref(prefs::kStabilityPluginStats);
 }
 
+// The server refuses data that doesn't have certain values.  crashcount and
+// launchcount are currently "required" in the "stability" group.
+// TODO(isherman): Stop writing these attributes specially once the migration to
+// protobufs is complete.
 void MetricsLog::WriteRequiredStabilityAttributes(PrefService* pref) {
-  // The server refuses data that doesn't have certain values.  crashcount and
-  // launchcount are currently "required" in the "stability" group.
-  WriteIntAttribute("launchcount",
-                    pref->GetInteger(prefs::kStabilityLaunchCount));
+  int launch_count = pref->GetInteger(prefs::kStabilityLaunchCount);
   pref->SetInteger(prefs::kStabilityLaunchCount, 0);
-  WriteIntAttribute("crashcount",
-                    pref->GetInteger(prefs::kStabilityCrashCount));
+  int crash_count = pref->GetInteger(prefs::kStabilityCrashCount);
   pref->SetInteger(prefs::kStabilityCrashCount, 0);
+
+  // Write the XML version.
+  WriteIntAttribute("launchcount", launch_count);
+  WriteIntAttribute("crashcount", crash_count);
+
+  // Write the protobuf version.
+  SystemProfileProto::Stability* stability =
+      uma_proto_.mutable_system_profile()->mutable_stability();
+  stability->set_launch_count(launch_count);
+  stability->set_crash_count(crash_count);
 }
 
 void MetricsLog::WriteRealtimeStabilityAttributes(PrefService* pref) {
@@ -237,71 +410,68 @@
   // Since these are "optional," only list ones that are non-zero, as the counts
   // are aggergated (summed) server side.
 
+  SystemProfileProto::Stability* stability =
+      uma_proto_.mutable_system_profile()->mutable_stability();
   int count = pref->GetInteger(prefs::kStabilityPageLoadCount);
   if (count) {
     WriteIntAttribute("pageloadcount", count);
+    stability->set_page_load_count(count);
     pref->SetInteger(prefs::kStabilityPageLoadCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilityRendererCrashCount);
   if (count) {
     WriteIntAttribute("renderercrashcount", count);
+    stability->set_renderer_crash_count(count);
     pref->SetInteger(prefs::kStabilityRendererCrashCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilityExtensionRendererCrashCount);
   if (count) {
     WriteIntAttribute("extensionrenderercrashcount", count);
+    stability->set_extension_renderer_crash_count(count);
     pref->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilityRendererHangCount);
   if (count) {
     WriteIntAttribute("rendererhangcount", count);
+    stability->set_renderer_hang_count(count);
     pref->SetInteger(prefs::kStabilityRendererHangCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilityChildProcessCrashCount);
   if (count) {
     WriteIntAttribute("childprocesscrashcount", count);
+    stability->set_child_process_crash_count(count);
     pref->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
   }
 
 #if defined(OS_CHROMEOS)
   count = pref->GetInteger(prefs::kStabilityOtherUserCrashCount);
   if (count) {
-    // TODO(kmixter): Write attribute once log server supports it
-    // and remove warning log.
-    // WriteIntAttribute("otherusercrashcount", count);
-    LOG(WARNING) << "Not yet able to send otherusercrashcount="
-                 << count;
+    stability->set_other_user_crash_count(count);
     pref->SetInteger(prefs::kStabilityOtherUserCrashCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilityKernelCrashCount);
   if (count) {
-    // TODO(kmixter): Write attribute once log server supports it
-    // and remove warning log.
-    // WriteIntAttribute("kernelcrashcount", count);
-    LOG(WARNING) << "Not yet able to send kernelcrashcount="
-                 << count;
+    stability->set_kernel_crash_count(count);
     pref->SetInteger(prefs::kStabilityKernelCrashCount, 0);
   }
 
   count = pref->GetInteger(prefs::kStabilitySystemUncleanShutdownCount);
   if (count) {
-    // TODO(kmixter): Write attribute once log server supports it
-    // and remove warning log.
-    // WriteIntAttribute("systemuncleanshutdowns", count);
-    LOG(WARNING) << "Not yet able to send systemuncleanshutdowns="
-                 << count;
+    stability->set_unclean_system_shutdown_count(count);
     pref->SetInteger(prefs::kStabilitySystemUncleanShutdownCount, 0);
   }
 #endif  // OS_CHROMEOS
 
   int64 recent_duration = GetIncrementalUptime(pref);
-  if (recent_duration)
+  if (recent_duration) {
     WriteInt64Attribute("uptimesec", recent_duration);
+    stability->set_uptime_sec(recent_duration);
+  }
 }
 
 void MetricsLog::WritePluginList(
@@ -311,32 +481,53 @@
   PluginPrefs* plugin_prefs = GetPluginPrefs();
 
   OPEN_ELEMENT_FOR_SCOPE("plugins");
-
+  SystemProfileProto* system_profile = uma_proto_.mutable_system_profile();
   for (std::vector<webkit::WebPluginInfo>::const_iterator iter =
            plugin_list.begin();
        iter != plugin_list.end(); ++iter) {
+    std::string base64_name_hash;
+    uint64 numeric_name_hash_ignored;
+    CreateHashes(UTF16ToUTF8(iter->name),
+                 &base64_name_hash,
+                 &numeric_name_hash_ignored);
+
+    std::string filename_bytes = iter->path.BaseName().AsUTF8Unsafe();
+    std::string base64_filename_hash;
+    uint64 numeric_filename_hash;
+    CreateHashes(filename_bytes,
+                 &base64_filename_hash,
+                 &numeric_filename_hash);
+
+    // Write the XML version.
     OPEN_ELEMENT_FOR_SCOPE("plugin");
 
     // Plugin name and filename are hashed for the privacy of those
     // testing unreleased new extensions.
-    WriteAttribute("name", CreateBase64Hash(UTF16ToUTF8(iter->name)));
-    std::string filename_bytes =
-#if defined(OS_WIN)
-        UTF16ToUTF8(iter->path.BaseName().value());
-#else
-        iter->path.BaseName().value();
-#endif
-    WriteAttribute("filename", CreateBase64Hash(filename_bytes));
+    WriteAttribute("name", base64_name_hash);
+    WriteAttribute("filename", base64_filename_hash);
     WriteAttribute("version", UTF16ToUTF8(iter->version));
     if (plugin_prefs)
       WriteIntAttribute("disabled", !plugin_prefs->IsPluginEnabled(*iter));
+
+    // Write the protobuf version.
+    SystemProfileProto::Plugin* plugin = system_profile->add_plugin();
+    SetPluginInfo(*iter, plugin_prefs, plugin);
   }
 }
 
 void MetricsLog::WriteInstallElement() {
+  std::string install_date = GetInstallDate();
+
+  // Write the XML version.
   OPEN_ELEMENT_FOR_SCOPE("install");
-  WriteAttribute("installdate", GetInstallDate());
+  WriteAttribute("installdate", install_date);
   WriteIntAttribute("buildid", 0);  // We're using appversion instead.
+
+  // Write the protobuf version.
+  int numeric_install_date;
+  bool success = base::StringToInt(install_date, &numeric_install_date);
+  DCHECK(success);
+  uma_proto_.mutable_system_profile()->set_install_date(numeric_install_date);
 }
 
 void MetricsLog::RecordEnvironment(
@@ -353,42 +544,96 @@
 
   WritePluginList(plugin_list);
 
-  WriteStabilityElement(pref);
+  WriteStabilityElement(plugin_list, pref);
 
+  SystemProfileProto* system_profile = uma_proto_.mutable_system_profile();
+  system_profile->set_application_locale(
+      content::GetContentClient()->browser()->GetApplicationLocale());
+
+  SystemProfileProto::Hardware* hardware = system_profile->mutable_hardware();
   {
+    std::string cpu_architecture = base::SysInfo::CPUArchitecture();
+
+    // Write the XML version.
     OPEN_ELEMENT_FOR_SCOPE("cpu");
-    WriteAttribute("arch", base::SysInfo::CPUArchitecture());
+    WriteAttribute("arch", cpu_architecture);
+
+    // Write the protobuf version.
+    hardware->set_cpu_architecture(cpu_architecture);
   }
 
   {
+    int system_memory_mb = base::SysInfo::AmountOfPhysicalMemoryMB();
+
+    // Write the XML version.
     OPEN_ELEMENT_FOR_SCOPE("memory");
-    WriteIntAttribute("mb", base::SysInfo::AmountOfPhysicalMemoryMB());
+    WriteIntAttribute("mb", system_memory_mb);
 #if defined(OS_WIN)
     WriteIntAttribute("dllbase", reinterpret_cast<int>(&__ImageBase));
 #endif
+
+    // Write the protobuf version.
+    hardware->set_system_ram_mb(system_memory_mb);
+#if defined(OS_WIN)
+    hardware->set_dll_base(reinterpret_cast<uint64>(&__ImageBase));
+#endif
   }
 
   {
+    std::string os_name = base::SysInfo::OperatingSystemName();
+    std::string os_version = base::SysInfo::OperatingSystemVersion();
+
+    // Write the XML version.
     OPEN_ELEMENT_FOR_SCOPE("os");
-    WriteAttribute("name",
-                   base::SysInfo::OperatingSystemName());
-    WriteAttribute("version",
-                   base::SysInfo::OperatingSystemVersion());
+    WriteAttribute("name", os_name);
+    WriteAttribute("version", os_version);
+
+    // Write the protobuf version.
+    SystemProfileProto::OS* os = system_profile->mutable_os();
+    os->set_name(os_name);
+    os->set_version(os_version);
   }
 
   {
     OPEN_ELEMENT_FOR_SCOPE("gpu");
-    content::GPUInfo gpu_info = GpuDataManager::GetInstance()->GetGPUInfo();
+    const content::GPUInfo& gpu_info =
+        GpuDataManager::GetInstance()->GetGPUInfo();
+    GpuPerformanceStats gpu_performance_stats =
+        GpuPerformanceStats::RetrieveGpuPerformanceStats();
+
+    // Write the XML version.
     WriteIntAttribute("vendorid", gpu_info.vendor_id);
     WriteIntAttribute("deviceid", gpu_info.device_id);
+
+    // Write the protobuf version.
+    SystemProfileProto::Hardware::Graphics* gpu = hardware->mutable_gpu();
+    gpu->set_vendor_id(gpu_info.vendor_id);
+    gpu->set_device_id(gpu_info.device_id);
+    gpu->set_driver_version(gpu_info.driver_version);
+    gpu->set_driver_date(gpu_info.driver_date);
+    SystemProfileProto::Hardware::Graphics::PerformanceStatistics*
+        gpu_performance = gpu->mutable_performance_statistics();
+    gpu_performance->set_graphics_score(gpu_performance_stats.graphics);
+    gpu_performance->set_gaming_score(gpu_performance_stats.gaming);
+    gpu_performance->set_overall_score(gpu_performance_stats.overall);
   }
 
   {
-    OPEN_ELEMENT_FOR_SCOPE("display");
     const gfx::Size display_size = gfx::Screen::GetPrimaryMonitorSize();
-    WriteIntAttribute("xsize", display_size.width());
-    WriteIntAttribute("ysize", display_size.height());
-    WriteIntAttribute("screens", gfx::Screen::GetNumMonitors());
+    int display_width = display_size.width();
+    int display_height = display_size.height();
+    int screen_count = gfx::Screen::GetNumMonitors();
+
+    // Write the XML version.
+    OPEN_ELEMENT_FOR_SCOPE("display");
+    WriteIntAttribute("xsize", display_width);
+    WriteIntAttribute("ysize", display_height);
+    WriteIntAttribute("screens", screen_count);
+
+    // Write the protobuf version.
+    hardware->set_primary_screen_width(display_width);
+    hardware->set_primary_screen_height(display_height);
+    hardware->set_screen_count(screen_count);
   }
 
   {
@@ -493,6 +738,7 @@
 void MetricsLog::RecordOmniboxOpenedURL(const AutocompleteLog& log) {
   DCHECK(!locked_);
 
+  // Write the XML version.
   OPEN_ELEMENT_FOR_SCOPE("uielement");
   WriteAttribute("action", "autocomplete");
   WriteAttribute("targetidhash", "");
@@ -538,5 +784,25 @@
     }
   }
 
+  // Write the protobuf version.
+  OmniboxEventProto* omnibox_event = uma_proto_.add_omnibox_event();
+  omnibox_event->set_time(MetricsLogBase::GetCurrentTime());
+  if (log.tab_id != -1) {
+    // If we know what tab the autocomplete URL was opened in, log it.
+    omnibox_event->set_tab_id(log.tab_id);
+  }
+  omnibox_event->set_typed_length(log.text.length());
+  omnibox_event->set_selected_index(log.selected_index);
+  omnibox_event->set_completed_length(log.inline_autocompleted_length);
+  omnibox_event->set_input_type(AsOmniboxEventInputType(log.input_type));
+  for (AutocompleteResult::const_iterator i(log.result.begin());
+       i != log.result.end(); ++i) {
+    OmniboxEventProto::Suggestion* suggestion = omnibox_event->add_suggestion();
+    suggestion->set_provider(AsOmniboxEventProviderType(i->provider));
+    suggestion->set_result_type(AsOmniboxEventResultType(i->type));
+    suggestion->set_relevance(i->relevance);
+    suggestion->set_is_starred(i->starred);
+  }
+
   ++num_events_;
 }
diff --git a/chrome/browser/metrics/metrics_log.h b/chrome/browser/metrics/metrics_log.h
index a5a3504..5935139 100644
--- a/chrome/browser/metrics/metrics_log.h
+++ b/chrome/browser/metrics/metrics_log.h
@@ -65,18 +65,25 @@
   // Record recent delta for critical stability metrics.  We can't wait for a
   // restart to gather these, as that delay biases our observation away from
   // users that run happily for a looooong time.  We send increments with each
-  // uma log upload, just as we send histogram data.
-  void RecordIncrementalStabilityElements();
+  // uma log upload, just as we send histogram data.  Takes the list of
+  // installed plugins as a parameter because that can't be obtained
+  // synchronously from the UI thread.
+  void RecordIncrementalStabilityElements(
+      const std::vector<webkit::WebPluginInfo>& plugin_list);
 
  private:
   FRIEND_TEST_ALL_PREFIXES(MetricsLogTest, ChromeOSStabilityData);
 
   // Writes application stability metrics (as part of the profile log).
   // NOTE: Has the side-effect of clearing those counts.
-  void WriteStabilityElement(PrefService* pref);
+  void WriteStabilityElement(
+      const std::vector<webkit::WebPluginInfo>& plugin_list,
+      PrefService* pref);
 
   // Within stability group, write plugin crash stats.
-  void WritePluginStabilityElements(PrefService* pref);
+  void WritePluginStabilityElements(
+      const std::vector<webkit::WebPluginInfo>& plugin_list,
+      PrefService* pref);
 
   // Within the stability group, write required attributes.
   void WriteRequiredStabilityAttributes(PrefService* pref);
diff --git a/chrome/browser/metrics/metrics_log_serializer.cc b/chrome/browser/metrics/metrics_log_serializer.cc
index bbddf60..3369f85a 100644
--- a/chrome/browser/metrics/metrics_log_serializer.cc
+++ b/chrome/browser/metrics/metrics_log_serializer.cc
@@ -12,10 +12,12 @@
 #include "chrome/browser/prefs/scoped_user_pref_update.h"
 #include "chrome/common/pref_names.h"
 
+namespace {
+
 // The number of "initial" logs we're willing to save, and hope to send during
 // a future Chrome session.  Initial logs contain crash stats, and are pretty
 // small.
-static const size_t kMaxInitialLogsPersisted = 20;
+const size_t kMaxInitialLogsPersisted = 20;
 
 // The number of ongoing logs we're willing to save persistently, and hope to
 // send during a this or future sessions.  Note that each log may be pretty
@@ -26,15 +28,14 @@
 // A "standard shutdown" will create a small log, including just the data that
 // was not yet been transmitted, and that is normal (to have exactly one
 // ongoing_log_ at startup).
-static const size_t kMaxOngoingLogsPersisted = 8;
+const size_t kMaxOngoingLogsPersisted = 8;
 
 // We append (2) more elements to persisted lists: the size of the list and a
 // checksum of the elements.
-static const size_t kChecksumEntryCount = 2;
+const size_t kChecksumEntryCount = 2;
 
-namespace {
-// TODO(ziadh): This is here temporarily for a side experiment. Remove later
-// on.
+// TODO(isherman): Remove this histogram once it's confirmed that there are no
+// encoding failures for protobuf logs.
 enum LogStoreStatus {
   STORE_SUCCESS,    // Successfully presisted log.
   ENCODE_FAIL,      // Failed to encode log.
@@ -43,17 +44,23 @@
 };
 
 MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram(
-    MetricsLogSerializer::LogReadStatus status) {
-  UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall", status,
-                            MetricsLogSerializer::END_RECALL_STATUS);
+    MetricsLogSerializer::LogReadStatus status,
+    bool is_xml) {
+  if (is_xml) {
+    UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall",
+                              status, MetricsLogSerializer::END_RECALL_STATUS);
+  } else {
+    UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
+                              status, MetricsLogSerializer::END_RECALL_STATUS);
+  }
   return status;
 }
 
-// TODO(ziadh): Remove this when done with experiment.
 void MakeStoreStatusHistogram(LogStoreStatus status) {
   UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore2", status,
                             END_STORE_STATUS);
 }
+
 }  // namespace
 
 
@@ -61,52 +68,76 @@
 
 MetricsLogSerializer::~MetricsLogSerializer() {}
 
-void MetricsLogSerializer::SerializeLogs(const std::vector<std::string>& logs,
-                                         MetricsLogManager::LogType log_type) {
+void MetricsLogSerializer::SerializeLogs(
+    const std::vector<MetricsLogManager::SerializedLog>& logs,
+    MetricsLogManager::LogType log_type) {
   PrefService* local_state = g_browser_process->local_state();
   DCHECK(local_state);
-  const char* pref = NULL;
+  const char* pref_xml = NULL;
+  const char* pref_proto = NULL;
   size_t max_store_count = 0;
   switch (log_type) {
     case MetricsLogManager::INITIAL_LOG:
-      pref = prefs::kMetricsInitialLogs;
+      pref_xml = prefs::kMetricsInitialLogsXml;
+      pref_proto = prefs::kMetricsInitialLogsProto;
       max_store_count = kMaxInitialLogsPersisted;
       break;
     case MetricsLogManager::ONGOING_LOG:
-      pref = prefs::kMetricsOngoingLogs;
+      pref_xml = prefs::kMetricsOngoingLogsXml;
+      pref_proto = prefs::kMetricsOngoingLogsProto;
       max_store_count = kMaxOngoingLogsPersisted;
       break;
     default:
       NOTREACHED();
       return;
   };
-  ListPrefUpdate update(local_state, pref);
-  ListValue* pref_list = update.Get();
-  WriteLogsToPrefList(logs, max_store_count, pref_list);
+
+  // Write the XML version.
+  ListPrefUpdate update_xml(local_state, pref_xml);
+  WriteLogsToPrefList(logs, true, max_store_count, update_xml.Get());
+
+  // Write the protobuf version.
+  ListPrefUpdate update_proto(local_state, pref_proto);
+  WriteLogsToPrefList(logs, false, max_store_count, update_proto.Get());
 }
 
-void MetricsLogSerializer::DeserializeLogs(MetricsLogManager::LogType log_type,
-                                           std::vector<std::string>* logs) {
+void MetricsLogSerializer::DeserializeLogs(
+    MetricsLogManager::LogType log_type,
+    std::vector<MetricsLogManager::SerializedLog>* logs) {
   DCHECK(logs);
   PrefService* local_state = g_browser_process->local_state();
   DCHECK(local_state);
 
-  const char* pref = (log_type == MetricsLogManager::INITIAL_LOG) ?
-      prefs::kMetricsInitialLogs : prefs::kMetricsOngoingLogs;
-  const ListValue* unsent_logs = local_state->GetList(pref);
-  ReadLogsFromPrefList(*unsent_logs, logs);
+  const char* pref_xml;
+  const char* pref_proto;
+  if (log_type == MetricsLogManager::INITIAL_LOG) {
+    pref_xml = prefs::kMetricsInitialLogsXml;
+    pref_proto = prefs::kMetricsInitialLogsProto;
+  } else {
+    pref_xml = prefs::kMetricsOngoingLogsXml;
+    pref_proto = prefs::kMetricsOngoingLogsProto;
+  }
+
+  const ListValue* unsent_logs_xml = local_state->GetList(pref_xml);
+  const ListValue* unsent_logs_proto = local_state->GetList(pref_proto);
+  if (ReadLogsFromPrefList(*unsent_logs_xml, true, logs) == RECALL_SUCCESS) {
+    // In order to try to keep the data sent to both servers roughly in sync,
+    // only read the protobuf data if we read the XML data successfully.
+    ReadLogsFromPrefList(*unsent_logs_proto, false, logs);
+  }
 }
 
 // static
 void MetricsLogSerializer::WriteLogsToPrefList(
-    const std::vector<std::string>& local_list,
-    const size_t kMaxLocalListSize,
-    ListValue* list) {
+    const std::vector<MetricsLogManager::SerializedLog>& local_list,
+    bool is_xml,
+    size_t max_list_size,
+    base::ListValue* list) {
   list->Clear();
   size_t start = 0;
-  if (local_list.size() > kMaxLocalListSize)
-    start = local_list.size() - kMaxLocalListSize;
-  DCHECK(start <= local_list.size());
+  if (local_list.size() > max_list_size)
+    start = local_list.size() - max_list_size;
+  DCHECK_LE(start, local_list.size());
   if (local_list.size() <= start)
     return;
 
@@ -116,11 +147,13 @@
   base::MD5Context ctx;
   base::MD5Init(&ctx);
   std::string encoded_log;
-  for (std::vector<std::string>::const_iterator it = local_list.begin() + start;
+  for (std::vector<MetricsLogManager::SerializedLog>::const_iterator it =
+           local_list.begin() + start;
        it != local_list.end(); ++it) {
+    const std::string& value = is_xml ? it->xml : it->proto;
     // We encode the compressed log as Value::CreateStringValue() expects to
     // take a valid UTF8 string.
-    if (!base::Base64Encode(*it, &encoded_log)) {
+    if (!base::Base64Encode(value, &encoded_log)) {
       MakeStoreStatusHistogram(ENCODE_FAIL);
       list->Clear();
       return;
@@ -140,44 +173,58 @@
 // static
 MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList(
     const ListValue& list,
-    std::vector<std::string>* local_list) {
-  DCHECK(local_list->empty());
+    bool is_xml,
+    std::vector<MetricsLogManager::SerializedLog>* local_list) {
   if (list.GetSize() == 0)
-    return MakeRecallStatusHistogram(LIST_EMPTY);
+    return MakeRecallStatusHistogram(LIST_EMPTY, is_xml);
   if (list.GetSize() < 3)
-    return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL);
+    return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL, is_xml);
 
   // The size is stored at the beginning of the list.
   int size;
   bool valid = (*list.begin())->GetAsInteger(&size);
   if (!valid)
-    return MakeRecallStatusHistogram(LIST_SIZE_MISSING);
-
+    return MakeRecallStatusHistogram(LIST_SIZE_MISSING, is_xml);
   // Account for checksum and size included in the list.
   if (static_cast<unsigned int>(size) !=
       list.GetSize() - kChecksumEntryCount) {
-    return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION);
+    return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION, is_xml);
+  }
+
+  // Allocate strings for all of the logs we are going to read in.
+  // Do this ahead of time so that we can decode the string values directly into
+  // the elements of |local_list|, and thereby avoid making copies of the
+  // serialized logs, which can be fairly large.
+  if (is_xml) {
+    DCHECK(local_list->empty());
+    local_list->resize(size);
+  } else if (local_list->size() != static_cast<size_t>(size)) {
+    return MakeRecallStatusHistogram(XML_PROTO_MISMATCH, false);
   }
 
   base::MD5Context ctx;
   base::MD5Init(&ctx);
   std::string encoded_log;
-  std::string decoded_log;
+  size_t local_index = 0;
   for (ListValue::const_iterator it = list.begin() + 1;
-       it != list.end() - 1; ++it) {  // Last element is the checksum.
-    valid = (*it)->GetAsString(&encoded_log);
+       it != list.end() - 1;  // Last element is the checksum.
+       ++it, ++local_index) {
+    bool valid = (*it)->GetAsString(&encoded_log);
     if (!valid) {
       local_list->clear();
-      return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
+      return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION, is_xml);
     }
 
     base::MD5Update(&ctx, encoded_log);
 
+    DCHECK_LT(local_index, local_list->size());
+    std::string& decoded_log = is_xml ?
+        (*local_list)[local_index].xml :
+        (*local_list)[local_index].proto;
     if (!base::Base64Decode(encoded_log, &decoded_log)) {
       local_list->clear();
-      return MakeRecallStatusHistogram(DECODE_FAIL);
+      return MakeRecallStatusHistogram(DECODE_FAIL, is_xml);
     }
-    local_list->push_back(decoded_log);
   }
 
   // Verify checksum.
@@ -188,11 +235,11 @@
   valid = (*(list.end() - 1))->GetAsString(&recovered_md5);
   if (!valid) {
     local_list->clear();
-    return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION);
+    return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION, is_xml);
   }
   if (recovered_md5 != base::MD5DigestToBase16(digest)) {
     local_list->clear();
-    return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION);
+    return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION, is_xml);
   }
-  return MakeRecallStatusHistogram(RECALL_SUCCESS);
+  return MakeRecallStatusHistogram(RECALL_SUCCESS, is_xml);
 }
diff --git a/chrome/browser/metrics/metrics_log_serializer.h b/chrome/browser/metrics/metrics_log_serializer.h
index 69986b6..9a1e630 100644
--- a/chrome/browser/metrics/metrics_log_serializer.h
+++ b/chrome/browser/metrics/metrics_log_serializer.h
@@ -17,7 +17,7 @@
 // Serializer for persisting metrics logs to prefs.
 class MetricsLogSerializer : public MetricsLogManager::LogSerializer {
  public:
-  // Used to produce a historgram that keeps track of the status of recalling
+  // Used to produce a histogram that keeps track of the status of recalling
   // persisted per logs.
   enum LogReadStatus {
     RECALL_SUCCESS,         // We were able to correctly recall a persisted log.
@@ -28,8 +28,9 @@
     LOG_STRING_CORRUPTION,  // Failed to recover log string using GetAsString().
     CHECKSUM_CORRUPTION,    // Failed to verify checksum.
     CHECKSUM_STRING_CORRUPTION,  // Failed to recover checksum string using
-    // GetAsString().
+                                 // GetAsString().
     DECODE_FAIL,            // Failed to decode log.
+    XML_PROTO_MISMATCH,     // The XML and protobuf logs have inconsistent data.
     END_RECALL_STATUS       // Number of bins to use to create the histogram.
   };
 
@@ -37,23 +38,30 @@
   virtual ~MetricsLogSerializer();
 
   // Implementation of MetricsLogManager::LogSerializer
-  virtual void SerializeLogs(const std::vector<std::string>& logs,
-                             MetricsLogManager::LogType log_type) OVERRIDE;
-  virtual void DeserializeLogs(MetricsLogManager::LogType log_type,
-                               std::vector<std::string>* logs) OVERRIDE;
+  virtual void SerializeLogs(
+      const std::vector<MetricsLogManager::SerializedLog>& logs,
+      MetricsLogManager::LogType log_type) OVERRIDE;
+  virtual void DeserializeLogs(
+      MetricsLogManager::LogType log_type,
+      std::vector<MetricsLogManager::SerializedLog>* logs) OVERRIDE;
 
  private:
   // Encodes the textual log data from |local_list| and writes it to the given
-  // pref list, along with list size and checksum.
-  static void WriteLogsToPrefList(const std::vector<std::string>& local_list,
-                                  const size_t kMaxLocalListSize,
-                                  base::ListValue* list);
+  // pref list, along with list size and checksum.  If |is_xml| is true, writes
+  // the XML data from |local_list|; otherwise writes the protobuf data.
+  static void WriteLogsToPrefList(
+      const std::vector<MetricsLogManager::SerializedLog>& local_list,
+      bool is_xml,
+      size_t max_list_size,
+      base::ListValue* list);
 
   // Decodes and verifies the textual log data from |list|, populating
-  // |local_list| and returning a status code.
+  // |local_list| and returning a status code.  If |is_xml| is true, populates
+  // the XML data in |local_list|; otherwise populates the protobuf data.
   static LogReadStatus ReadLogsFromPrefList(
       const base::ListValue& list,
-      std::vector<std::string>* local_list);
+      bool is_xml,
+      std::vector<MetricsLogManager::SerializedLog>* local_list);
 
   FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, EmptyLogList);
   FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, SingleElementLogList);
diff --git a/chrome/browser/metrics/metrics_log_serializer_unittest.cc b/chrome/browser/metrics/metrics_log_serializer_unittest.cc
index 59c257b..f023a7c 100644
--- a/chrome/browser/metrics/metrics_log_serializer_unittest.cc
+++ b/chrome/browser/metrics/metrics_log_serializer_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
 
@@ -8,42 +8,46 @@
 #include "chrome/browser/metrics/metrics_log_serializer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace {
-  class MetricsLogSerializerTest : public ::testing::Test {
-  };
-}
+typedef MetricsLogManager::SerializedLog SerializedLog;
 
-static const size_t kMaxLocalListSize = 3;
+namespace {
+
+const size_t kMaxLocalListSize = 3;
+
+}  // namespace
+
+class MetricsLogSerializerTest : public ::testing::Test {
+};
 
 // Store and retrieve empty list.
 TEST(MetricsLogSerializerTest, EmptyLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
+  std::vector<SerializedLog> local_list;
 
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
   EXPECT_EQ(0U, list.GetSize());
 
   local_list.clear();  // ReadLogsFromPrefList() expects empty |local_list|.
-  EXPECT_EQ(MetricsLogSerializer::LIST_EMPTY,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::LIST_EMPTY,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
   EXPECT_EQ(0U, local_list.size());
 }
 
 // Store and retrieve a single log value.
 TEST(MetricsLogSerializerTest, SingleElementLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.push_back("Hello world!");
-  EXPECT_EQ(1U, local_list.size());
+  std::vector<SerializedLog> local_list(1);
+  local_list[0].xml = "Hello world!";
 
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
 
   // |list| will now contain the following:
   // [1, Base64Encode("Hello world!"), MD5("Hello world!")].
-  EXPECT_EQ(3U, list.GetSize());
+  ASSERT_EQ(3U, list.GetSize());
 
   // Examine each element.
   ListValue::const_iterator it = list.begin();
@@ -66,55 +70,56 @@
   EXPECT_TRUE(it == list.end());  // Reached end of list.
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::RECALL_SUCCESS,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::RECALL_SUCCESS,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
   EXPECT_EQ(1U, local_list.size());
 }
 
 // Store elements greater than the limit.
 TEST(MetricsLogSerializerTest, OverLimitLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.push_back("one");
-  local_list.push_back("two");
-  local_list.push_back("three");
-  local_list.push_back("four");
-  EXPECT_EQ(4U, local_list.size());
+  std::vector<SerializedLog> local_list(4);
+  local_list[0].proto = "one";
+  local_list[1].proto = "two";
+  local_list[2].proto = "three";
+  local_list[3].proto = "four";
 
   std::string expected_first;
-  base::Base64Encode(local_list[local_list.size() - kMaxLocalListSize],
+  base::Base64Encode(local_list[local_list.size() - kMaxLocalListSize].proto,
                      &expected_first);
   std::string expected_last;
-  base::Base64Encode(local_list[local_list.size() - 1],
+  base::Base64Encode(local_list[local_list.size() - 1].proto,
                      &expected_last);
 
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
-                                            &list);
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, false,
+                                            kMaxLocalListSize, &list);
   EXPECT_EQ(kMaxLocalListSize + 2, list.GetSize());
 
   std::string actual_first;
   EXPECT_TRUE((*(list.begin() + 1))->GetAsString(&actual_first));
-  EXPECT_TRUE(expected_first == actual_first);
+  EXPECT_EQ(expected_first, actual_first);
 
   std::string actual_last;
   EXPECT_TRUE((*(list.end() - 2))->GetAsString(&actual_last));
-  EXPECT_TRUE(expected_last == actual_last);
+  EXPECT_EQ(expected_last, actual_last);
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::RECALL_SUCCESS,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::RECALL_SUCCESS,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
   EXPECT_EQ(kMaxLocalListSize, local_list.size());
 }
 
 // Induce LIST_SIZE_TOO_SMALL corruption
 TEST(MetricsLogSerializerTest, SmallRecoveredListSize) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.push_back("Hello world!");
-  EXPECT_EQ(1U, local_list.size());
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  std::vector<SerializedLog> local_list(1);
+  local_list[0].xml = "Hello world!";
+
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
   EXPECT_EQ(3U, list.GetSize());
 
@@ -123,19 +128,20 @@
   EXPECT_EQ(2U, list.GetSize());
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::LIST_SIZE_TOO_SMALL,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::LIST_SIZE_TOO_SMALL,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
 }
 
 // Remove size from the stored list.
 TEST(MetricsLogSerializerTest, RemoveSizeFromLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.push_back("one");
-  local_list.push_back("two");
+  std::vector<SerializedLog> local_list(2);
+  local_list[0].xml = "one";
+  local_list[1].xml = "two";
   EXPECT_EQ(2U, local_list.size());
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
   EXPECT_EQ(4U, list.GetSize());
 
@@ -143,18 +149,19 @@
   EXPECT_EQ(3U, list.GetSize());
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::LIST_SIZE_MISSING,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::LIST_SIZE_MISSING,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
 }
 
 // Corrupt size of stored list.
 TEST(MetricsLogSerializerTest, CorruptSizeOfLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.push_back("Hello world!");
-  EXPECT_EQ(1U, local_list.size());
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  std::vector<SerializedLog> local_list(1);
+  local_list[0].xml = "Hello world!";
+
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
   EXPECT_EQ(3U, list.GetSize());
 
@@ -163,19 +170,19 @@
   EXPECT_EQ(3U, list.GetSize());
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::LIST_SIZE_CORRUPTION,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::LIST_SIZE_CORRUPTION,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
 }
 
 // Corrupt checksum of stored list.
 TEST(MetricsLogSerializerTest, CorruptChecksumOfLogList) {
   ListValue list;
-  std::vector<std::string> local_list;
 
-  local_list.clear();
-  local_list.push_back("Hello world!");
-  EXPECT_EQ(1U, local_list.size());
-  MetricsLogSerializer::WriteLogsToPrefList(local_list, kMaxLocalListSize,
+  std::vector<SerializedLog> local_list(1);
+  local_list[0].xml = "Hello world!";
+
+  MetricsLogSerializer::WriteLogsToPrefList(local_list, true, kMaxLocalListSize,
                                             &list);
   EXPECT_EQ(3U, list.GetSize());
 
@@ -187,6 +194,7 @@
   EXPECT_EQ(3U, list.GetSize());
 
   local_list.clear();
-  EXPECT_EQ(MetricsLogSerializer::CHECKSUM_CORRUPTION,
-            MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list));
+  EXPECT_EQ(
+      MetricsLogSerializer::CHECKSUM_CORRUPTION,
+      MetricsLogSerializer::ReadLogsFromPrefList(list, true, &local_list));
 }
diff --git a/chrome/browser/metrics/metrics_log_unittest.cc b/chrome/browser/metrics/metrics_log_unittest.cc
index edea42b..4c4b555 100644
--- a/chrome/browser/metrics/metrics_log_unittest.cc
+++ b/chrome/browser/metrics/metrics_log_unittest.cc
@@ -14,18 +14,15 @@
 #include "chrome/test/base/testing_pref_service.h"
 #include "googleurl/src/gurl.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/plugins/webplugininfo.h"
 
 using base::TimeDelta;
 
 namespace {
-  class MetricsLogTest : public testing::Test {
-  };
-};
-
 
 // Since buildtime is highly variable, this function will scan an output log and
 // replace it with a consistent number.
-static void NormalizeBuildtime(std::string* xml_encoded) {
+void NormalizeBuildtime(std::string* xml_encoded) {
   std::string prefix = "buildtime=\"";
   const char postfix = '\"';
   size_t offset = xml_encoded->find(prefix);
@@ -43,54 +40,6 @@
   xml_encoded->replace(offset, postfix_position - offset, "123246");
 }
 
-TEST(MetricsLogTest, EmptyRecord) {
-  std::string expected_output = base::StringPrintf(
-      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
-      "appversion=\"%s\"/>", MetricsLog::GetVersionString().c_str());
-
-  MetricsLog log("bogus client ID", 0);
-  log.CloseLog();
-
-  int size = log.GetEncodedLogSize();
-  ASSERT_GT(size, 0);
-
-  std::string encoded;
-  // Leave room for the NUL terminator.
-  ASSERT_TRUE(log.GetEncodedLog(WriteInto(&encoded, size + 1), size));
-  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
-  NormalizeBuildtime(&encoded);
-  NormalizeBuildtime(&expected_output);
-
-  ASSERT_EQ(expected_output, encoded);
-}
-
-#if defined(OS_CHROMEOS)
-TEST(MetricsLogTest, ChromeOSEmptyRecord) {
-  std::string expected_output = base::StringPrintf(
-      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
-      "appversion=\"%s\" hardwareclass=\"sample-class\"/>",
-      MetricsLog::GetVersionString().c_str());
-
-  MetricsLog log("bogus client ID", 0);
-  log.set_hardware_class("sample-class");
-  log.CloseLog();
-
-  int size = log.GetEncodedLogSize();
-  ASSERT_GT(size, 0);
-
-  std::string encoded;
-  // Leave room for the NUL terminator.
-  ASSERT_TRUE(log.GetEncodedLog(WriteInto(&encoded, size + 1), size));
-  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
-  NormalizeBuildtime(&encoded);
-  NormalizeBuildtime(&expected_output);
-
-  ASSERT_EQ(expected_output, encoded);
-}
-#endif  // OS_CHROMEOS
-
-namespace {
-
 class NoTimeMetricsLog : public MetricsLog {
  public:
   NoTimeMetricsLog(std::string client_id, int session_id):
@@ -103,103 +52,12 @@
   }
 };
 
-};  // namespace
+}  // namespace
 
-TEST(MetricsLogTest, WindowEvent) {
-  std::string expected_output = base::StringPrintf(
-      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
-          "appversion=\"%s\">\n"
-      " <window action=\"create\" windowid=\"0\" session=\"0\" time=\"\"/>\n"
-      " <window action=\"open\" windowid=\"1\" parent=\"0\" "
-          "session=\"0\" time=\"\"/>\n"
-      " <window action=\"close\" windowid=\"1\" parent=\"0\" "
-          "session=\"0\" time=\"\"/>\n"
-      " <window action=\"destroy\" windowid=\"0\" session=\"0\" time=\"\"/>\n"
-      "</log>", MetricsLog::GetVersionString().c_str());
-
-  NoTimeMetricsLog log("bogus client ID", 0);
-  log.RecordWindowEvent(MetricsLog::WINDOW_CREATE, 0, -1);
-  log.RecordWindowEvent(MetricsLog::WINDOW_OPEN, 1, 0);
-  log.RecordWindowEvent(MetricsLog::WINDOW_CLOSE, 1, 0);
-  log.RecordWindowEvent(MetricsLog::WINDOW_DESTROY, 0, -1);
-  log.CloseLog();
-
-  ASSERT_EQ(4, log.num_events());
-
-  int size = log.GetEncodedLogSize();
-  ASSERT_GT(size, 0);
-
-  std::string encoded;
-  // Leave room for the NUL terminator.
-  ASSERT_TRUE(log.GetEncodedLog(WriteInto(&encoded, size + 1), size));
-  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
-  NormalizeBuildtime(&encoded);
-  NormalizeBuildtime(&expected_output);
-
-  ASSERT_EQ(expected_output, encoded);
-}
-
-TEST(MetricsLogTest, LoadEvent) {
-  std::string expected_output = base::StringPrintf(
-      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
-          "appversion=\"%s\">\n"
-      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
-          "origin=\"link\" session=\"0\" time=\"\"/>\n"
-      "</log>", MetricsLog::GetVersionString().c_str());
-
-  NoTimeMetricsLog log("bogus client ID", 0);
-  log.RecordLoadEvent(3, GURL("http://google.com"),
-                      content::PAGE_TRANSITION_LINK, 1,
-                      TimeDelta::FromMilliseconds(7219));
-
-  log.CloseLog();
-
-  ASSERT_EQ(1, log.num_events());
-
-  int size = log.GetEncodedLogSize();
-  ASSERT_GT(size, 0);
-
-  std::string encoded;
-  // Leave room for the NUL terminator.
-  ASSERT_TRUE(log.GetEncodedLog(WriteInto(&encoded, size + 1), size));
-  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
-  NormalizeBuildtime(&encoded);
-  NormalizeBuildtime(&expected_output);
-
-  ASSERT_EQ(expected_output, encoded);
-}
+class MetricsLogTest : public testing::Test {
+};
 
 #if defined(OS_CHROMEOS)
-TEST(MetricsLogTest, ChromeOSLoadEvent) {
-  std::string expected_output = base::StringPrintf(
-      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
-          "appversion=\"%s\" hardwareclass=\"sample-class\">\n"
-      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
-          "origin=\"link\" session=\"0\" time=\"\"/>\n"
-      "</log>", MetricsLog::GetVersionString().c_str());
-
-  NoTimeMetricsLog log("bogus client ID", 0);
-  log.RecordLoadEvent(3, GURL("http://google.com"),
-                      content::PAGE_TRANSITION_LINK, 1,
-                      TimeDelta::FromMilliseconds(7219));
-  log.set_hardware_class("sample-class");
-  log.CloseLog();
-
-  ASSERT_EQ(1, log.num_events());
-
-  int size = log.GetEncodedLogSize();
-  ASSERT_GT(size, 0);
-
-  std::string encoded;
-  // Leave room for the NUL terminator.
-  ASSERT_TRUE(log.GetEncodedLog(WriteInto(&encoded, size + 1), size));
-  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
-  NormalizeBuildtime(&encoded);
-  NormalizeBuildtime(&expected_output);
-
-  ASSERT_EQ(expected_output, encoded);
-}
-
 TEST(MetricsLogTest, ChromeOSStabilityData) {
   NoTimeMetricsLog log("bogus client ID", 0);
   TestingPrefService prefs;
@@ -209,16 +67,19 @@
   prefs.SetInteger(prefs::kStabilityOtherUserCrashCount, 11);
   prefs.SetInteger(prefs::kStabilityKernelCrashCount, 12);
   prefs.SetInteger(prefs::kStabilitySystemUncleanShutdownCount, 13);
+
+  std::vector<webkit::WebPluginInfo> plugins;
+
   std::string expected_output = base::StringPrintf(
       "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
           "appversion=\"%s\">\n"
       "<stability stuff>\n", MetricsLog::GetVersionString().c_str());
   // Expect 3 warnings about not yet being able to send the
   // Chrome OS stability stats.
-  log.WriteStabilityElement(&prefs);
+  log.WriteStabilityElement(plugins, &prefs);
   log.CloseLog();
 
-  int size = log.GetEncodedLogSize();
+  int size = log.GetEncodedLogSizeXml();
   ASSERT_GT(size, 0);
 
   EXPECT_EQ(0, prefs.GetInteger(prefs::kStabilityChildProcessCrashCount));
@@ -228,7 +89,7 @@
 
   std::string encoded;
   // Leave room for the NUL terminator.
-  bool encoding_result = log.GetEncodedLog(
+  bool encoding_result = log.GetEncodedLogXml(
       WriteInto(&encoded, size + 1), size);
   ASSERT_TRUE(encoding_result);
 
@@ -244,31 +105,4 @@
   EXPECT_EQ(std::string::npos,
             encoded.find(" systemuncleanshutdowns="));
 }
-
 #endif  // OS_CHROMEOS
-
-// Make sure our ID hashes are the same as what we see on the server side.
-TEST(MetricsLogTest, CreateHash) {
-  static const struct {
-    std::string input;
-    std::string output;
-  } cases[] = {
-    {"Back", "0x0557fa923dcee4d0"},
-    {"Forward", "0x67d2f6740a8eaebf"},
-    {"NewTab", "0x290eb683f96572f1"},
-  };
-
-  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
-    std::string hash_string = MetricsLog::CreateHash(cases[i].input);
-
-    // Convert to hex string
-    // We're only checking the first 8 bytes, because that's what
-    // the metrics server uses.
-    std::string hash_hex = "0x";
-    for (size_t j = 0; j < 8; j++) {
-      base::StringAppendF(&hash_hex, "%02x",
-                          static_cast<uint8>(hash_string.data()[j]));
-    }
-    EXPECT_EQ(cases[i].output, hash_hex);
-  }
-};
diff --git a/chrome/browser/metrics/metrics_service.cc b/chrome/browser/metrics/metrics_service.cc
index 1626a5c..fdf9bfde 100644
--- a/chrome/browser/metrics/metrics_service.cc
+++ b/chrome/browser/metrics/metrics_service.cc
@@ -182,6 +182,7 @@
 #include "content/public/browser/plugin_service.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/url_fetcher.h"
+#include "net/base/load_flags.h"
 #include "webkit/plugins/webplugininfo.h"
 
 // TODO(port): port browser_distribution.h.
@@ -200,30 +201,44 @@
 using content::ChildProcessData;
 using content::PluginService;
 
-// Check to see that we're being called on only one thread.
-static bool IsSingleThreaded();
+namespace {
 
-static const char kMetricsType[] = "application/vnd.mozilla.metrics.bz2";
+// Check to see that we're being called on only one thread.
+bool IsSingleThreaded() {
+  static base::PlatformThreadId thread_id = 0;
+  if (!thread_id)
+    thread_id = base::PlatformThread::CurrentId();
+  return base::PlatformThread::CurrentId() == thread_id;
+}
+
+const char kMetricsTypeXml[] = "application/vnd.mozilla.metrics.bz2";
+const char kMetricsTypeProto[] = "application/vnd.chrome.uma";
+
+const char kServerUrlXml[] =
+    "https://clients4.google.com/firefox/metrics/collect";
+const char kServerUrlProto[] = "https://clients4.google.com/uma/v2";
 
 // The delay, in seconds, after starting recording before doing expensive
 // initialization work.
-static const int kInitializationDelaySeconds = 30;
+const int kInitializationDelaySeconds = 30;
 
 // This specifies the amount of time to wait for all renderers to send their
 // data.
-static const int kMaxHistogramGatheringWaitDuration = 60000;  // 60 seconds.
+const int kMaxHistogramGatheringWaitDuration = 60000;  // 60 seconds.
 
 // The maximum number of events in a log uploaded to the UMA server.
-static const int kEventLimit = 2400;
+const int kEventLimit = 2400;
 
 // If an upload fails, and the transmission was over this byte count, then we
 // will discard the log, and not try to retransmit it.  We also don't persist
 // the log to the prefs for transmission during the next chrome session if this
 // limit is exceeded.
-static const int kUploadLogAvoidRetransmitSize = 50000;
+const size_t kUploadLogAvoidRetransmitSize = 50000;
 
 // Interval, in minutes, between state saves.
-static const int kSaveStateIntervalMinutes = 5;
+const int kSaveStateIntervalMinutes = 5;
+
+}
 
 // static
 MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ =
@@ -323,8 +338,10 @@
                                    0);
   local_state->RegisterIntegerPref(prefs::kNumFoldersInOtherBookmarkFolder, 0);
   local_state->RegisterIntegerPref(prefs::kNumKeywords, 0);
-  local_state->RegisterListPref(prefs::kMetricsInitialLogs);
-  local_state->RegisterListPref(prefs::kMetricsOngoingLogs);
+  local_state->RegisterListPref(prefs::kMetricsInitialLogsXml);
+  local_state->RegisterListPref(prefs::kMetricsOngoingLogsXml);
+  local_state->RegisterListPref(prefs::kMetricsInitialLogsProto);
+  local_state->RegisterListPref(prefs::kMetricsOngoingLogsProto);
 
   local_state->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0);
   local_state->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0);
@@ -357,15 +374,16 @@
 
   local_state->ClearPref(prefs::kStabilityPluginStats);
 
-  local_state->ClearPref(prefs::kMetricsInitialLogs);
-  local_state->ClearPref(prefs::kMetricsOngoingLogs);
+  local_state->ClearPref(prefs::kMetricsInitialLogsXml);
+  local_state->ClearPref(prefs::kMetricsOngoingLogsXml);
+  local_state->ClearPref(prefs::kMetricsInitialLogsProto);
+  local_state->ClearPref(prefs::kMetricsOngoingLogsProto);
 }
 
 MetricsService::MetricsService()
     : recording_active_(false),
       reporting_active_(false),
       state_(INITIALIZED),
-      current_fetch_(NULL),
       io_thread_(NULL),
       idle_since_last_transmission_(false),
       next_window_id_(0),
@@ -628,11 +646,13 @@
 
 void MetricsService::InitializeMetricsState() {
 #if defined(OS_POSIX)
-  server_url_ = L"https://clients4.google.com/firefox/metrics/collect";
+  server_url_xml_ = ASCIIToUTF16(kServerUrlXml);
+  server_url_proto_ = ASCIIToUTF16(kServerUrlProto);
   network_stats_server_ = "chrome.googleechotest.com";
 #else
   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
-  server_url_ = dist->GetStatsServerURL();
+  server_url_xml_ = dist->GetStatsServerURL();
+  server_url_proto_ = ASCIIToUTF16(kServerUrlProto);
   network_stats_server_ = dist->GetNetworkStatsServer();
 #endif
 
@@ -840,7 +860,7 @@
   MetricsLog* current_log =
       static_cast<MetricsLog*>(log_manager_.current_log());
   DCHECK(current_log);
-  current_log->RecordIncrementalStabilityElements();
+  current_log->RecordIncrementalStabilityElements(plugins_);
   RecordCurrentHistograms();
 
   log_manager_.StageCurrentLogForUpload();
@@ -935,8 +955,9 @@
   // If somehow there is a fetch in progress, we return and hope things work
   // out. The scheduler isn't informed since if this happens, the scheduler
   // will get a response from the upload.
-  DCHECK(!current_fetch_.get());
-  if (current_fetch_.get())
+  DCHECK(!current_fetch_xml_.get());
+  DCHECK(!current_fetch_proto_.get());
+  if (current_fetch_xml_.get() || current_fetch_proto_.get())
     return;
 
   // This function should only be called as the callback from an ansynchronous
@@ -966,7 +987,8 @@
 
   PrepareFetchWithStagedLog();
 
-  if (!current_fetch_.get()) {
+  if (!current_fetch_xml_.get()) {
+    DCHECK(!current_fetch_proto_.get());
     // Compression failed, and log discarded :-/.
     log_manager_.DiscardStagedLog();
     scheduler_->UploadCancelled();
@@ -974,11 +996,19 @@
     // compressed that, so that we can signal that we're losing logs.
     return;
   }
+  // Currently, the staged log for the protobuf version of the data is discarded
+  // after we create the URL request, so that there is no chance for
+  // re-transmission in case the corresponding XML request fails.  We will
+  // handle protobuf failures more carefully once that becomes the main
+  // pipeline, i.e. once we switch away from the XML pipeline.
+  DCHECK(current_fetch_proto_.get() || !log_manager_.has_staged_log_proto());
 
   DCHECK(!waiting_for_asynchronus_reporting_step_);
 
   waiting_for_asynchronus_reporting_step_ = true;
-  current_fetch_->Start();
+  current_fetch_xml_->Start();
+  if (current_fetch_proto_.get())
+    current_fetch_proto_->Start();
 
   HandleIdleSinceLastTransmission(true);
 }
@@ -1052,13 +1082,40 @@
 
 void MetricsService::PrepareFetchWithStagedLog() {
   DCHECK(!log_manager_.staged_log_text().empty());
-  DCHECK(!current_fetch_.get());
 
-  current_fetch_.reset(content::URLFetcher::Create(
-      GURL(WideToUTF16(server_url_)), content::URLFetcher::POST, this));
-  current_fetch_->SetRequestContext(
+  // Prepare the XML version.
+  DCHECK(!current_fetch_xml_.get());
+  current_fetch_xml_.reset(content::URLFetcher::Create(
+      GURL(server_url_xml_), content::URLFetcher::POST, this));
+  current_fetch_xml_->SetRequestContext(
       g_browser_process->system_request_context());
-  current_fetch_->SetUploadData(kMetricsType, log_manager_.staged_log_text());
+  current_fetch_xml_->SetUploadData(kMetricsTypeXml,
+                                    log_manager_.staged_log_text().xml);
+  // We already drop cookies server-side, but we might as well strip them out
+  // client-side as well.
+  current_fetch_xml_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+                                   net::LOAD_DO_NOT_SEND_COOKIES);
+
+  // Prepare the protobuf version.
+  DCHECK(!current_fetch_proto_.get());
+  if (log_manager_.has_staged_log_proto()) {
+    current_fetch_proto_.reset(content::URLFetcher::Create(
+        GURL(server_url_proto_), content::URLFetcher::POST, this));
+    current_fetch_proto_->SetRequestContext(
+        g_browser_process->system_request_context());
+    current_fetch_proto_->SetUploadData(kMetricsTypeProto,
+                                        log_manager_.staged_log_text().proto);
+    // We already drop cookies server-side, but we might as well strip them out
+    // client-side as well.
+    current_fetch_proto_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+                                       net::LOAD_DO_NOT_SEND_COOKIES);
+
+    // Discard the protobuf version of the staged log, so that we will avoid
+    // re-uploading it even if we need to re-upload the XML version.
+    // TODO(isherman): Handle protobuf upload failures more gracefully once we
+    // transition away from the XML-based pipeline.
+    log_manager_.DiscardStagedLogProto();
+  }
 }
 
 static const char* StatusToString(const net::URLRequestStatus& status) {
@@ -1084,30 +1141,71 @@
   }
 }
 
+// We need to wait for two responses: the response to the XML upload, and the
+// response to the protobuf upload.  For now, only the XML upload's response
+// affects decisions like whether to retry the upload, whether to abandon the
+// upload because it is too large, etc.  However, we still need to wait for the
+// protobuf upload, as we cannot reset |current_fetch_proto_| until we have
+// confirmation that the network request was sent; and the easiest way to do
+// that is to wait for the response.  In case the XML upload's response arrives
+// first, we cache that response until the protobuf upload's response also
+// arrives.
+//
+// Note that if the XML upload succeeds but the protobuf upload fails, we will
+// not retry the protobuf upload.  If the XML upload fails while the protobuf
+// upload succeeds, we will still avoid re-uploading the protobuf data because
+// we "zap" the data after the first upload attempt.  This means that we might
+// lose protobuf uploads when XML ones succeed; but we will never duplicate any
+// protobuf uploads.  Protobuf failures should be rare enough to where this
+// should be ok while we have the two pipelines running in parallel.
 void MetricsService::OnURLFetchComplete(const content::URLFetcher* source) {
   DCHECK(waiting_for_asynchronus_reporting_step_);
+
+  // We're not allowed to re-use the existing |URLFetcher|s, so free them here.
+  scoped_ptr<content::URLFetcher> s;
+  if (source == current_fetch_xml_.get()) {
+    s.reset(current_fetch_xml_.release());
+
+    // Cache the XML responses, in case we still need to wait for the protobuf
+    // response.
+    response_code_ = source->GetResponseCode();
+    response_status_ = StatusToString(source->GetStatus());
+    source->GetResponseAsString(&response_data_);
+  } else if (source == current_fetch_proto_.get()) {
+    s.reset(current_fetch_proto_.release());
+  } else {
+    NOTREACHED();
+    return;
+  }
+
+  // If we're still waiting for one of the responses, keep waiting...
+  if (current_fetch_xml_.get() || current_fetch_proto_.get())
+    return;
+
+  // We should only be able to reach here once we've received responses to both
+  // the XML and the protobuf requests.  We should always have the response code
+  // available.
+  DCHECK_NE(response_code_, content::URLFetcher::RESPONSE_CODE_INVALID);
   waiting_for_asynchronus_reporting_step_ = false;
-  DCHECK(current_fetch_.get());
-  // We're not allowed to re-use it. Delete it on function exit since we use it.
-  scoped_ptr<content::URLFetcher> s(current_fetch_.release());
+
 
   // Confirm send so that we can move on.
-  VLOG(1) << "METRICS RESPONSE CODE: " << source->GetResponseCode()
-          << " status=" << StatusToString(source->GetStatus());
+  VLOG(1) << "METRICS RESPONSE CODE: " << response_code_
+          << " status=" << response_status_;
 
-  bool upload_succeeded = source->GetResponseCode() == 200;
+  bool upload_succeeded = response_code_ == 200;
 
   // Provide boolean for error recovery (allow us to ignore response_code).
   bool discard_log = false;
 
   if (!upload_succeeded &&
-      (log_manager_.staged_log_text().length() >
-          static_cast<size_t>(kUploadLogAvoidRetransmitSize))) {
+      log_manager_.staged_log_text().xml.length() >
+          kUploadLogAvoidRetransmitSize) {
     UMA_HISTOGRAM_COUNTS(
         "UMA.Large Rejected Log was Discarded",
-        static_cast<int>(log_manager_.staged_log_text().length()));
+        static_cast<int>(log_manager_.staged_log_text().xml.length()));
     discard_log = true;
-  } else if (source->GetResponseCode() == 400) {
+  } else if (response_code_ == 400) {
     // Bad syntax.  Retransmission won't work.
     UMA_HISTOGRAM_COUNTS("UMA.Unacceptable_Log_Discarded", state_);
     discard_log = true;
@@ -1115,12 +1213,10 @@
 
   if (!upload_succeeded && !discard_log) {
     VLOG(1) << "METRICS: transmission attempt returned a failure code: "
-            << source->GetResponseCode() << ". Verify network connectivity";
+            << response_code_ << ". Verify network connectivity";
     LogBadResponseCode();
   } else {  // Successful receipt (or we are discarding log).
-    std::string data;
-    source->GetResponseAsString(&data);
-    VLOG(1) << "METRICS RESPONSE DATA: " << data;
+    VLOG(1) << "METRICS RESPONSE DATA: " << response_data_;
     switch (state_) {
       case INITIAL_LOG_READY:
         state_ = SENDING_OLD_LOGS;
@@ -1147,7 +1243,7 @@
 
   // Error 400 indicates a problem with the log, not with the server, so
   // don't consider that a sign that the server is in trouble.
-  bool server_is_healthy = upload_succeeded || source->GetResponseCode() == 400;
+  bool server_is_healthy = upload_succeeded || response_code_ == 400;
 
   scheduler_->UploadFinished(server_is_healthy,
                              log_manager_.has_unsent_logs());
@@ -1155,16 +1251,21 @@
   // Collect network stats if UMA upload succeeded.
   if (server_is_healthy && io_thread_)
     chrome_browser_net::CollectNetworkStats(network_stats_server_, io_thread_);
+
+  // Reset the cached response data.
+  response_code_ = content::URLFetcher::RESPONSE_CODE_INVALID;
+  response_data_ = std::string();
+  response_status_ = std::string();
 }
 
 void MetricsService::LogBadResponseCode() {
   VLOG(1) << "Verify your metrics logs are formatted correctly.  Verify server "
-             "is active at " << server_url_;
+             "is active at " << server_url_xml_;
   if (!log_manager_.has_staged_log()) {
     VLOG(1) << "METRICS: Recorder shutdown during log transmission.";
   } else {
     VLOG(1) << "METRICS: transmission retry being scheduled for "
-            << log_manager_.staged_log_text();
+            << log_manager_.staged_log_text().xml;
   }
 }
 
@@ -1536,13 +1637,6 @@
           type == content::PROCESS_TYPE_PPAPI_PLUGIN);
 }
 
-static bool IsSingleThreaded() {
-  static base::PlatformThreadId thread_id = 0;
-  if (!thread_id)
-    thread_id = base::PlatformThread::CurrentId();
-  return base::PlatformThread::CurrentId() == thread_id;
-}
-
 #if defined(OS_CHROMEOS)
 void MetricsService::StartExternalMetrics() {
   external_metrics_ = new chromeos::ExternalMetrics;
diff --git a/chrome/browser/metrics/metrics_service.h b/chrome/browser/metrics/metrics_service.h
index 56f9776..8803905b 100644
--- a/chrome/browser/metrics/metrics_service.h
+++ b/chrome/browser/metrics/metrics_service.h
@@ -340,10 +340,18 @@
   std::vector<webkit::WebPluginInfo> plugins_;
 
   // The outstanding transmission appears as a URL Fetch operation.
-  scoped_ptr<content::URLFetcher> current_fetch_;
+  scoped_ptr<content::URLFetcher> current_fetch_xml_;
+  scoped_ptr<content::URLFetcher> current_fetch_proto_;
 
-  // The URL for the metrics server.
-  std::wstring server_url_;
+  // Cached responses from the XML request while we wait for a response to the
+  // protubuf request.
+  int response_code_;
+  std::string response_status_;
+  std::string response_data_;
+
+  // The URLs for the XML and protobuf metrics servers.
+  string16 server_url_xml_;
+  string16 server_url_proto_;
 
   // The TCP/UDP echo server to collect network connectivity stats.
   std::string network_stats_server_;