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_;
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 1a690c6..67884a2 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2071,6 +2071,7 @@
         'common/mac/mock_launchd.h',
         'common/mac/objc_method_swizzle_unittest.mm',
         'common/mac/objc_zombie_unittest.mm',
+        'common/metrics/metrics_log_base_unittest.cc',
         'common/metrics/metrics_log_manager_unittest.cc',
         'common/multi_process_lock_unittest.cc',
         'common/net/gaia/gaia_auth_fetcher_unittest.cc',
diff --git a/chrome/common/metrics/metrics_log_base.cc b/chrome/common/metrics/metrics_log_base.cc
index b2c42f6..d6a467f 100644
--- a/chrome/common/metrics/metrics_log_base.cc
+++ b/chrome/common/metrics/metrics_log_base.cc
@@ -9,10 +9,13 @@
 #include "base/md5.h"
 #include "base/perftimer.h"
 #include "base/string_number_conversions.h"
+#include "base/sys_byteorder.h"
 #include "base/sys_info.h"
 #include "base/third_party/nspr/prtime.h"
 #include "base/utf_string_conversions.h"
 #include "chrome/common/logging_chrome.h"
+#include "chrome/common/metrics/proto/histogram_event.pb.h"
+#include "chrome/common/metrics/proto/user_action_event.pb.h"
 #include "libxml/xmlwriter.h"
 
 #define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name)
@@ -20,11 +23,8 @@
 using base::Histogram;
 using base::Time;
 using base::TimeDelta;
-
-// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
-#if defined(OS_WIN)
-extern "C" IMAGE_DOS_HEADER __ImageBase;
-#endif
+using metrics::HistogramEventProto;
+using metrics::UserActionEventProto;
 
 namespace {
 
@@ -33,6 +33,43 @@
   return reinterpret_cast<const unsigned char*>(input);
 }
 
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+  return id.size() < 16;
+}
+
+// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
+inline uint64 HashToUInt64(const std::string& hash) {
+  uint64 value;
+  DCHECK_GE(hash.size(), sizeof(value));
+  memcpy(&value, hash.data(), sizeof(value));
+  return base::htonll(value);
+}
+
+// Creates an MD5 hash of the given value, and returns hash as a byte buffer
+// encoded as an std::string.
+std::string CreateHash(const std::string& value) {
+  base::MD5Context context;
+  base::MD5Init(&context);
+  base::MD5Update(&context, value);
+
+  base::MD5Digest digest;
+  base::MD5Final(&digest, &context);
+
+  std::string hash(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
+
+  // The following log is VERY helpful when folks add some named histogram into
+  // the code, but forgot to update the descriptive list of histograms.  When
+  // that happens, all we get to see (server side) is a hash of the histogram
+  // name.  We can then use this logging to find out what histogram name was
+  // being hashed to a given MD5 value by just running the version of Chromium
+  // in question with --enable-logging.
+  DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << HashToUInt64(hash)
+           << "]";
+
+  return hash;
+}
+
 }  // namespace
 
 class MetricsLogBase::XmlWrapper {
@@ -92,11 +129,23 @@
       locked_(false),
       xml_wrapper_(new XmlWrapper),
       num_events_(0) {
+  int64_t build_time = GetBuildTime();
 
+  // Write the XML version.
   StartElement("log");
   WriteAttribute("clientid", client_id_);
-  WriteInt64Attribute("buildtime", GetBuildTime());
+  WriteInt64Attribute("buildtime", build_time);
   WriteAttribute("appversion", version_string);
+
+  // Write the protobuf version.
+  if (IsTestingID(client_id_)) {
+    uma_proto_.set_client_id(0);
+  } else {
+    uma_proto_.set_client_id(HashToUInt64(CreateHash(client_id)));
+  }
+  uma_proto_.set_session_id(session_id);
+  uma_proto_.mutable_system_profile()->set_build_timestamp(build_time);
+  uma_proto_.mutable_system_profile()->set_app_version(version_string);
 }
 
 MetricsLogBase::~MetricsLogBase() {
@@ -109,6 +158,38 @@
   xml_wrapper_ = NULL;
 }
 
+// static
+void MetricsLogBase::CreateHashes(const std::string& string,
+                                  std::string* base64_encoded_hash,
+                                  uint64* numeric_hash) {
+  std::string hash = CreateHash(string);
+
+  std::string encoded_digest;
+  if (base::Base64Encode(hash, &encoded_digest))
+    DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
+
+  *base64_encoded_hash = encoded_digest;
+  *numeric_hash = HashToUInt64(hash);
+}
+
+// static
+int64 MetricsLogBase::GetBuildTime() {
+  static int64 integral_build_time = 0;
+  if (!integral_build_time) {
+    Time time;
+    const char* kDateTime = __DATE__ " " __TIME__ " GMT";
+    bool result = Time::FromString(kDateTime, &time);
+    DCHECK(result);
+    integral_build_time = static_cast<int64>(time.ToTimeT());
+  }
+  return integral_build_time;
+}
+
+// static
+int64 MetricsLogBase::GetCurrentTime() {
+  return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
 void MetricsLogBase::CloseLog() {
   DCHECK(!locked_);
   locked_ = true;
@@ -120,6 +201,9 @@
   DCHECK_GE(result, 0);
 
 #if defined(OS_CHROMEOS)
+  // TODO(isherman): Once the XML pipeline is deprecated, there will be no need
+  // to track the hardware class in a separate member variable and only write it
+  // out when the log is closed.
   xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc());
   if (!hardware_class_.empty()) {
     // The hardware class is determined after the first ongoing log is
@@ -127,6 +211,10 @@
     // attribute when the log is closed instead.
     xmlNewProp(root, UnsignedChar("hardwareclass"),
                UnsignedChar(hardware_class_.c_str()));
+
+    // Write to the protobuf too.
+    uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+        hardware_class_);
   }
 
   // Flattens the XML tree into a character buffer.
@@ -142,79 +230,51 @@
 #endif  // OS_CHROMEOS
 }
 
-int MetricsLogBase::GetEncodedLogSize() {
+int MetricsLogBase::GetEncodedLogSizeXml() {
   DCHECK(locked_);
   CHECK(xml_wrapper_);
   CHECK(xml_wrapper_->buffer());
   return xml_wrapper_->buffer()->use;
 }
 
-bool MetricsLogBase::GetEncodedLog(char* buffer, int buffer_size) {
+bool MetricsLogBase::GetEncodedLogXml(char* buffer, int buffer_size) {
   DCHECK(locked_);
-  if (buffer_size < GetEncodedLogSize())
+  if (buffer_size < GetEncodedLogSizeXml())
     return false;
 
-  memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSize());
+  memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSizeXml());
   return true;
 }
 
-std::string MetricsLogBase::GetEncodedLogString() {
+void MetricsLogBase::GetEncodedLogProto(std::string* encoded_log) {
   DCHECK(locked_);
-  return std::string(reinterpret_cast<char*>(xml_wrapper_->buffer()->content));
+  uma_proto_.SerializeToString(encoded_log);
 }
 
 int MetricsLogBase::GetElapsedSeconds() {
   return static_cast<int>((Time::Now() - start_time_).InSeconds());
 }
 
-std::string MetricsLogBase::CreateHash(const std::string& value) {
-  base::MD5Context ctx;
-  base::MD5Init(&ctx);
-  base::MD5Update(&ctx, value);
-
-  base::MD5Digest digest;
-  base::MD5Final(&digest, &ctx);
-
-  uint64 reverse_uint64;
-  // UMA only uses first 8 chars of hash. We use the above uint64 instead
-  // of a unsigned char[8] so that we don't run into strict aliasing issues
-  // in the LOG statement below when trying to interpret reverse as a uint64.
-  unsigned char* reverse = reinterpret_cast<unsigned char *>(&reverse_uint64);
-  DCHECK(arraysize(digest.a) >= sizeof(reverse_uint64));
-  for (size_t i = 0; i < sizeof(reverse_uint64); ++i)
-    reverse[i] = digest.a[sizeof(reverse_uint64) - i - 1];
-  // The following log is VERY helpful when folks add some named histogram into
-  // the code, but forgot to update the descriptive list of histograms.  When
-  // that happens, all we get to see (server side) is a hash of the histogram
-  // name.  We can then use this logging to find out what histogram name was
-  // being hashed to a given MD5 value by just running the version of Chromium
-  // in question with --enable-logging.
-  DVLOG(1) << "Metrics: Hash numeric [" << value
-           << "]=[" << reverse_uint64 << "]";
-  return std::string(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
-}
-
-std::string MetricsLogBase::CreateBase64Hash(const std::string& string) {
-  std::string encoded_digest;
-  if (base::Base64Encode(CreateHash(string), &encoded_digest)) {
-    DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
-    return encoded_digest;
-  }
-  return std::string();
-}
-
 void MetricsLogBase::RecordUserAction(const char* key) {
   DCHECK(!locked_);
 
-  std::string command_hash = CreateBase64Hash(key);
-  if (command_hash.empty()) {
+  std::string base64_hash;
+  uint64 numeric_hash;
+  CreateHashes(key, &base64_hash, &numeric_hash);
+  if (base64_hash.empty()) {
     NOTREACHED() << "Unable generate encoded hash of command: " << key;
     return;
   }
 
+  // Write the XML version.
   OPEN_ELEMENT_FOR_SCOPE("uielement");
   WriteAttribute("action", "command");
-  WriteAttribute("targetidhash", command_hash);
+  WriteAttribute("targetidhash", base64_hash);
+
+  // Write the protobuf version.
+  UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+  user_action->set_name_hash(numeric_hash);
+  user_action->set_time(MetricsLogBase::GetCurrentTime());
 
   // TODO(jhughes): Properly track windows.
   WriteIntAttribute("window", 0);
@@ -310,7 +370,7 @@
 }
 
 void MetricsLogBase::WriteAttribute(const std::string& name,
-                                const std::string& value) {
+                                    const std::string& value) {
   DCHECK(!locked_);
   DCHECK(!name.empty());
 
@@ -359,19 +419,6 @@
   DCHECK_GE(result, 0);
 }
 
-// static
-int64 MetricsLogBase::GetBuildTime() {
-  static int64 integral_build_time = 0;
-  if (!integral_build_time) {
-    Time time;
-    const char* kDateTime = __DATE__ " " __TIME__ " GMT";
-    bool result = Time::FromString(kDateTime, &time);
-    DCHECK(result);
-    integral_build_time = static_cast<int64>(time.ToTimeT());
-  }
-  return integral_build_time;
-}
-
 // TODO(JAR): A The following should really be part of the histogram class.
 // Internal state is being needlessly exposed, and it would be hard to reuse
 // this code. If we moved this into the Histogram class, then we could use
@@ -387,7 +434,14 @@
 
   OPEN_ELEMENT_FOR_SCOPE("histogram");
 
-  WriteAttribute("name", CreateBase64Hash(histogram.histogram_name()));
+  std::string base64_name_hash;
+  uint64 numeric_name_hash;
+  CreateHashes(histogram.histogram_name(),
+               &base64_name_hash,
+               &numeric_name_hash);
+
+  // Write the XML version.
+  WriteAttribute("name", base64_name_hash);
 
   WriteInt64Attribute("sum", snapshot.sum());
   // TODO(jar): Remove sumsquares when protobuffer accepts this as optional.
@@ -401,4 +455,19 @@
       WriteIntAttribute("count", snapshot.counts(i));
     }
   }
+
+  // Write the protobuf version.
+  HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
+  histogram_proto->set_name_hash(numeric_name_hash);
+  histogram_proto->set_sum(snapshot.sum());
+
+  for (size_t i = 0; i < histogram.bucket_count(); ++i) {
+    if (snapshot.counts(i)) {
+      HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+      bucket->set_min(histogram.ranges(i));
+      bucket->set_max(histogram.ranges(i + 1));
+      bucket->set_bucket_index(i);
+      bucket->set_count(snapshot.counts(i));
+    }
+  }
 }
diff --git a/chrome/common/metrics/metrics_log_base.h b/chrome/common/metrics/metrics_log_base.h
index 62981bed..3c493dd7 100644
--- a/chrome/common/metrics/metrics_log_base.h
+++ b/chrome/common/metrics/metrics_log_base.h
@@ -14,6 +14,7 @@
 #include "base/basictypes.h"
 #include "base/metrics/histogram.h"
 #include "base/time.h"
+#include "chrome/common/metrics/proto/chrome_user_metrics_extension.pb.h"
 #include "content/public/common/page_transition_types.h"
 
 class GURL;
@@ -29,6 +30,24 @@
                  const std::string& version_string);
   virtual ~MetricsLogBase();
 
+  // Computes the MD5 hash of the given string.
+  // Fills |base64_encoded_hash| with the hash, encoded in base64.
+  // Fills |numeric_hash| with the first 8 bytes of the hash.
+  static void CreateHashes(const std::string& string,
+                           std::string* base64_encoded_hash,
+                           uint64* numeric_hash);
+
+  // Get the GMT buildtime for the current binary, expressed in seconds since
+  // Januray 1, 1970 GMT.
+  // The value is used to identify when a new build is run, so that previous
+  // reliability stats, from other builds, can be abandoned.
+  static int64 GetBuildTime();
+
+  // Convenience function to return the current time at a resolution in seconds.
+  // This wraps base::TimeTicks, and hence provides an abstract time that is
+  // always incrementing for use in measuring time durations.
+  static int64 GetCurrentTime();
+
   // Records a user-initiated action.
   void RecordUserAction(const char* key);
 
@@ -64,10 +83,12 @@
   // record.  They can only be called after CloseLog() has been called.
   // GetEncodedLog returns false if buffer_size is less than
   // GetEncodedLogSize();
-  int GetEncodedLogSize();
-  bool GetEncodedLog(char* buffer, int buffer_size);
-  // Returns an empty string on failure.
-  std::string GetEncodedLogString();
+  int GetEncodedLogSizeXml();
+  bool GetEncodedLogXml(char* buffer, int buffer_size);
+
+  // Fills |encoded_log| with the protobuf representation of the record.  Can
+  // only be called after CloseLog() has been called.
+  void GetEncodedLogProto(std::string* encoded_log);
 
   // Returns the amount of time in seconds that this log has been in use.
   int GetElapsedSeconds();
@@ -78,19 +99,6 @@
     hardware_class_ = hardware_class;
   }
 
-  // Creates an MD5 hash of the given value, and returns hash as a byte
-  // buffer encoded as a std::string.
-  static std::string CreateHash(const std::string& value);
-
-  // Return a base64-encoded MD5 hash of the given string.
-  static std::string CreateBase64Hash(const std::string& string);
-
-  // Get the GMT buildtime for the current binary, expressed in seconds since
-  // Januray 1, 1970 GMT.
-  // The value is used to identify when a new build is run, so that previous
-  // reliability stats, from other builds, can be abandoned.
-  static int64 GetBuildTime();
-
  protected:
   class XmlWrapper;
 
@@ -156,6 +164,9 @@
 
   int num_events_;  // the number of events recorded in this log
 
+  // Stores the protocol buffer representation for this log.
+  metrics::ChromeUserMetricsExtension uma_proto_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
 };
diff --git a/chrome/common/metrics/metrics_log_base_unittest.cc b/chrome/common/metrics/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..610c11ca
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_base_unittest.cc
@@ -0,0 +1,195 @@
+// 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.
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/format_macros.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "chrome/common/metrics/metrics_log_base.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace {
+
+// Since buildtime is highly variable, this function will scan an output log and
+// replace it with a consistent number.
+void NormalizeBuildtime(std::string* xml_encoded) {
+  std::string prefix = "buildtime=\"";
+  const char postfix = '\"';
+  size_t offset = xml_encoded->find(prefix);
+  ASSERT_NE(std::string::npos, offset);
+  offset += prefix.size();
+  size_t postfix_position = xml_encoded->find(postfix, offset);
+  ASSERT_NE(std::string::npos, postfix_position);
+  for (size_t i = offset; i < postfix_position; ++i) {
+    char digit = xml_encoded->at(i);
+    ASSERT_GE(digit, '0');
+    ASSERT_LE(digit, '9');
+  }
+
+  // Insert a single fake buildtime.
+  xml_encoded->replace(offset, postfix_position - offset, "123246");
+}
+
+class NoTimeMetricsLogBase : public MetricsLogBase {
+ public:
+  NoTimeMetricsLogBase(std::string client_id,
+                       int session_id,
+                       std::string version_string):
+      MetricsLogBase(client_id, session_id, version_string) {}
+  virtual ~NoTimeMetricsLogBase() {}
+
+  // Override this so that output is testable.
+  virtual std::string GetCurrentTimeString() OVERRIDE {
+    return std::string();
+  }
+};
+
+}  // namespace
+
+TEST(MetricsLogBaseTest, EmptyRecord) {
+  std::string expected_output =
+#if defined(OS_CHROMEOS)
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+      "appversion=\"bogus version\" hardwareclass=\"sample-class\"/>";
+#else
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+      "appversion=\"bogus version\"/>";
+#endif  // OS_CHROMEOS
+
+  MetricsLogBase log("bogus client ID", 0, "bogus version");
+  log.set_hardware_class("sample-class");
+  log.CloseLog();
+
+  int size = log.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+TEST(MetricsLogBaseTest, WindowEvent) {
+  std::string expected_output =
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\">\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>";
+
+  NoTimeMetricsLogBase log("bogus client ID", 0, "bogus version");
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_CREATE, 0, -1);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_OPEN, 1, 0);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_CLOSE, 1, 0);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_DESTROY, 0, -1);
+  log.CloseLog();
+
+  ASSERT_EQ(4, log.num_events());
+
+  int size = log.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+TEST(MetricsLogBaseTest, LoadEvent) {
+  std::string expected_output =
+#if defined(OS_CHROMEOS)
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\" hardwareclass=\"sample-class\">\n"
+      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
+          "origin=\"link\" session=\"0\" time=\"\"/>\n"
+      "</log>";
+#else
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\">\n"
+      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
+          "origin=\"link\" session=\"0\" time=\"\"/>\n"
+      "</log>";
+#endif  // OS_CHROMEOS
+
+  NoTimeMetricsLogBase log("bogus client ID", 0, "bogus version");
+  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.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+// Make sure our ID hashes are the same as what we see on the server side.
+TEST(MetricsLogBaseTest, CreateHashes) {
+  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;
+    uint64 hash_numeric;
+    MetricsLogBase::CreateHashes(cases[i].input, &hash_string, &hash_numeric);
+
+    // Verify the numeric representation.
+    {
+      std::string hash_numeric_hex =
+          base::StringPrintf("0x%016" PRIx64, hash_numeric);
+      EXPECT_EQ(cases[i].output, hash_numeric_hex);
+    }
+
+    // Verify the string representation.
+    {
+      std::string hash_string_decoded;
+      ASSERT_TRUE(base::Base64Decode(hash_string, &hash_string_decoded));
+
+      // Convert to hex string
+      // We're only checking the first 8 bytes, because that's what
+      // the metrics server uses.
+      std::string hash_string_hex = "0x";
+      ASSERT_GE(hash_string_decoded.size(), 8U);
+      for (size_t j = 0; j < 8; j++) {
+        base::StringAppendF(&hash_string_hex, "%02x",
+                            static_cast<uint8>(hash_string_decoded.data()[j]));
+      }
+      EXPECT_EQ(cases[i].output, hash_string_hex);
+    }
+  }
+};
diff --git a/chrome/common/metrics/metrics_log_manager.cc b/chrome/common/metrics/metrics_log_manager.cc
index 32fe51c..a917bcf 100644
--- a/chrome/common/metrics/metrics_log_manager.cc
+++ b/chrome/common/metrics/metrics_log_manager.cc
@@ -14,10 +14,23 @@
 #include "base/string_util.h"
 #include "chrome/common/metrics/metrics_log_base.h"
 
+namespace {
+
+// Used to keep track of discarded protobuf logs without having to track xml and
+// protobuf logs in separate lists.
+const char kDiscardedLog[] = "Log discarded";
+
+}  // anonymous namespace
+
 MetricsLogManager::MetricsLogManager() : max_ongoing_log_store_size_(0) {}
 
 MetricsLogManager::~MetricsLogManager() {}
 
+bool MetricsLogManager::SerializedLog::empty() const {
+  DCHECK_EQ(xml.empty(), proto.empty());
+  return xml.empty();
+}
+
 void MetricsLogManager::BeginLoggingWithLog(MetricsLogBase* log) {
   DCHECK(!current_log_.get());
   current_log_.reset(log);
@@ -31,12 +44,21 @@
 }
 
 bool MetricsLogManager::has_staged_log() const {
-  return staged_log_.get() || !compressed_staged_log_text_.empty();
+  return staged_log_.get() || !staged_log_text().empty();
+}
+
+bool MetricsLogManager::has_staged_log_proto() const {
+  return has_staged_log() && staged_log_text().proto != kDiscardedLog;
 }
 
 void MetricsLogManager::DiscardStagedLog() {
   staged_log_.reset();
-  compressed_staged_log_text_.clear();
+  staged_log_text_.xml.clear();
+  staged_log_text_.proto.clear();
+}
+
+void MetricsLogManager::DiscardStagedLogProto() {
+  staged_log_text_.proto = kDiscardedLog;
 }
 
 void MetricsLogManager::DiscardCurrentLog() {
@@ -56,21 +78,26 @@
 
 void MetricsLogManager::StoreStagedLogAsUnsent(LogType log_type) {
   DCHECK(has_staged_log());
+
   // If compressing the log failed, there's nothing to store.
-  if (compressed_staged_log_text_.empty())
+  if (staged_log_text().empty())
     return;
 
   if (log_type == INITIAL_LOG) {
-    unsent_initial_logs_.push_back(compressed_staged_log_text_);
+    unsent_initial_logs_.push_back(staged_log_text_);
   } else {
     // If it's too large, just note that and discard it.
     if (max_ongoing_log_store_size_ &&
-        compressed_staged_log_text_.length() > max_ongoing_log_store_size_) {
+        staged_log_text().xml.length() > max_ongoing_log_store_size_) {
+      // TODO(isherman): We probably want a similar check for protobufs, but we
+      // don't want to prevent XML upload just because the protobuf version is
+      // too long.  In practice, I'm pretty sure the XML version should always
+      // be longer, or at least on the same order of magnitude in length.
       UMA_HISTOGRAM_COUNTS(
           "UMA.Large Accumulated Log Not Persisted",
-          static_cast<int>(compressed_staged_log_text_.length()));
+          static_cast<int>(staged_log_text().xml.length()));
     } else {
-      unsent_ongoing_logs_.push_back(compressed_staged_log_text_);
+      unsent_ongoing_logs_.push_back(staged_log_text_);
     }
   }
   DiscardStagedLog();
@@ -78,11 +105,13 @@
 
 void MetricsLogManager::StageNextStoredLogForUpload() {
   // Prioritize initial logs for uploading.
-  std::vector<std::string>* source_list = unsent_initial_logs_.empty() ?
-      &unsent_ongoing_logs_ : &unsent_initial_logs_;
+  std::vector<SerializedLog>* source_list =
+      unsent_initial_logs_.empty() ?
+      &unsent_ongoing_logs_ :
+      &unsent_initial_logs_;
   DCHECK(!source_list->empty());
-  DCHECK(compressed_staged_log_text_.empty());
-  compressed_staged_log_text_ = source_list->back();
+  DCHECK(staged_log_text().empty());
+  staged_log_text_ = source_list->back();
   source_list->pop_back();
 }
 
@@ -103,16 +132,20 @@
 }
 
 void MetricsLogManager::CompressStagedLog() {
-  int text_size = staged_log_->GetEncodedLogSize();
+  int text_size = staged_log_->GetEncodedLogSizeXml();
   std::string staged_log_text;
   DCHECK_GT(text_size, 0);
-  staged_log_->GetEncodedLog(WriteInto(&staged_log_text, text_size + 1),
-                             text_size);
+  staged_log_->GetEncodedLogXml(WriteInto(&staged_log_text, text_size + 1),
+                                text_size);
 
-  bool success = Bzip2Compress(staged_log_text, &compressed_staged_log_text_);
+  bool success = Bzip2Compress(staged_log_text, &staged_log_text_.xml);
   if (success) {
     // Allow security-conscious users to see all metrics logs that we send.
     DVLOG(1) << "METRICS LOG: " << staged_log_text;
+
+    // Note that we only save the protobuf version if we succeeded in
+    // compressing the XML, so that the two data streams are the same.
+    staged_log_->GetEncodedLogProto(&staged_log_text_.proto);
   } else {
     NOTREACHED() << "Failed to compress log for transmission.";
   }
diff --git a/chrome/common/metrics/metrics_log_manager.h b/chrome/common/metrics/metrics_log_manager.h
index 3eb04f0..9ae20ad 100644
--- a/chrome/common/metrics/metrics_log_manager.h
+++ b/chrome/common/metrics/metrics_log_manager.h
@@ -22,6 +22,17 @@
   MetricsLogManager();
   ~MetricsLogManager();
 
+  // Stores both XML and protocol buffer serializations for a log.
+  struct SerializedLog {
+   public:
+    // Exposed to reduce code churn as we transition from the XML pipeline to
+    // the protocol buffer pipeline.
+    bool empty() const;
+
+    std::string xml;
+    std::string proto;
+  };
+
   // Takes ownership of |log|, and makes it the current_log.
   // This should only be called if there is not a current log.
   void BeginLoggingWithLog(MetricsLogBase* log);
@@ -36,13 +47,25 @@
   // Note that this returns true even if compressing the log text failed.
   bool has_staged_log() const;
 
-  // The compressed text of the staged log. Empty if there is no staged log,
-  // or if compression of the staged log failed.
-  const std::string& staged_log_text() { return compressed_staged_log_text_; }
+  // Returns true if there is a protobuf log that needs to be uploaded.
+  // In the case that an XML upload needs to be re-issued due to a previous
+  // failure, |has_staged_log()| will return true while this returns false.
+  bool has_staged_log_proto() const;
 
-  // Discards the staged log.
+  // The text of the staged log, in compressed XML or protobuf format. Empty if
+  // there is no staged log, or if compression of the staged log failed.
+  const SerializedLog& staged_log_text() const {
+    return staged_log_text_;
+  }
+
+  // Discards the staged log (both the XML and the protobuf data).
   void DiscardStagedLog();
 
+  // Discards the protobuf data in the staged log.
+  // This is useful to prevent needlessly re-issuing successful protobuf uploads
+  // due to XML upload failures.
+  void DiscardStagedLogProto();
+
   // Closes and discards |current_log|.
   void DiscardCurrentLog();
 
@@ -91,13 +114,13 @@
 
     // Serializes |logs| to persistent storage, replacing any previously
     // serialized logs of the same type.
-    virtual void SerializeLogs(const std::vector<std::string>& logs,
+    virtual void SerializeLogs(const std::vector<SerializedLog>& logs,
                                LogType log_type) = 0;
 
     // Populates |logs| with logs of type |log_type| deserialized from
     // persistent storage.
     virtual void DeserializeLogs(LogType log_type,
-                                 std::vector<std::string>* logs) = 0;
+                                 std::vector<SerializedLog>* logs) = 0;
   };
 
   // Sets the serializer to use for persisting and loading logs; takes ownership
@@ -115,8 +138,8 @@
   void LoadPersistedUnsentLogs();
 
  private:
-  // Compresses staged_log_ and stores the result in
-  // compressed_staged_log_text_.
+  // Compresses |staged_log_| and stores the result in
+  // |compressed_staged_xml_log_text_|.
   void CompressStagedLog();
 
   // Compresses the text in |input| using bzip2, store the result in |output|.
@@ -130,21 +153,25 @@
 
   // The log that we are currently transmiting, or about to try to transmit.
   // Note that when using StageNextStoredLogForUpload, this can be NULL while
-  // compressed_staged_log_text_ is non-NULL.
+  // |compressed_staged_xml_log_text_| is non-NULL.
   scoped_ptr<MetricsLogBase> staged_log_;
 
   // Helper class to handle serialization/deserialization of logs for persistent
   // storage. May be NULL.
   scoped_ptr<LogSerializer> log_serializer_;
 
-  // The compressed text of the staged log, ready for upload to the server.
-  std::string compressed_staged_log_text_;
+  // The text representations of the staged log, ready for upload to the server.
+  // The first item in the pair is the compressed XML representation; the second
+  // is the protobuf representation.
+  SerializedLog staged_log_text_;
 
   // Logs from a previous session that have not yet been sent.
+  // The first item in each pair is the XML representation; the second item is
+  // the protobuf representation.
   // Note that the vector has the oldest logs listed first (early in the
   // vector), and we'll discard old logs if we have gathered too many logs.
-  std::vector<std::string> unsent_initial_logs_;
-  std::vector<std::string> unsent_ongoing_logs_;
+  std::vector<SerializedLog> unsent_initial_logs_;
+  std::vector<SerializedLog> unsent_ongoing_logs_;
 
   size_t max_ongoing_log_store_size_;
 
diff --git a/chrome/common/metrics/metrics_log_manager_unittest.cc b/chrome/common/metrics/metrics_log_manager_unittest.cc
index 0373186..43b2071 100644
--- a/chrome/common/metrics/metrics_log_manager_unittest.cc
+++ b/chrome/common/metrics/metrics_log_manager_unittest.cc
@@ -2,27 +2,31 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
+#include <utility>
+#include <vector>
+
 #include "chrome/common/metrics/metrics_log_base.h"
 #include "chrome/common/metrics/metrics_log_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#include <string>
-#include <vector>
+typedef MetricsLogManager::SerializedLog SerializedLog;
 
 namespace {
+
 class MetricsLogManagerTest : public testing::Test {
 };
 
 // Dummy serializer that just stores logs in memory.
 class DummyLogSerializer : public MetricsLogManager::LogSerializer {
  public:
-  virtual void SerializeLogs(const std::vector<std::string>& logs,
+  virtual void SerializeLogs(const std::vector<SerializedLog>& logs,
                              MetricsLogManager::LogType log_type) {
     persisted_logs_[log_type] = logs;
   }
 
   virtual void DeserializeLogs(MetricsLogManager::LogType log_type,
-                               std::vector<std::string>* logs) {
+                               std::vector<SerializedLog>* logs) {
     ASSERT_NE(static_cast<void*>(NULL), logs);
     *logs = persisted_logs_[log_type];
   }
@@ -33,9 +37,10 @@
   }
 
   // In-memory "persitent storage".
-  std::vector<std::string> persisted_logs_[2];
+  std::vector<SerializedLog> persisted_logs_[2];
 };
-};  // namespace
+
+}  // namespace
 
 TEST(MetricsLogManagerTest, StandardFlow) {
   MetricsLogManager log_manager;
@@ -109,8 +114,8 @@
 }
 
 TEST(MetricsLogManagerTest, StoreAndLoad) {
-  std::vector<std::string> initial_logs;
-  std::vector<std::string> ongoing_logs;
+  std::vector<SerializedLog> initial_logs;
+  std::vector<SerializedLog> ongoing_logs;
 
   // Set up some in-progress logging in a scoped log manager simulating the
   // leadup to quitting, then persist as would be done on quit.
@@ -119,7 +124,8 @@
     DummyLogSerializer* serializer = new DummyLogSerializer;
     log_manager.set_log_serializer(serializer);
     // Simulate a log having already been unsent from a previous session.
-    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG].push_back("a");
+    SerializedLog log = {"xml", "proto"};
+    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG].push_back(log);
     EXPECT_FALSE(log_manager.has_unsent_logs());
     log_manager.LoadPersistedUnsentLogs();
     EXPECT_TRUE(log_manager.has_unsent_logs());
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 653039be..fb81da4d 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -984,16 +984,20 @@
 
 // Array of strings that are each UMA logs that were supposed to be sent in the
 // first minute of a browser session. These logs include things like crash count
-// info, etc.
-const char kMetricsInitialLogs[] =
+// info, etc.  Currently we store both XML and protobuf versions of these logs.
+const char kMetricsInitialLogsXml[] =
     "user_experience_metrics.initial_logs";
+const char kMetricsInitialLogsProto[] =
+    "user_experience_metrics.initial_logs_as_protobufs";
 
 // Array of strings that are each UMA logs that were not sent because the
 // browser terminated before these accumulated metrics could be sent.  These
 // logs typically include histograms and memory reports, as well as ongoing
-// user activities.
-const char kMetricsOngoingLogs[] =
+// user activities.  Currently we store both XML and protobuf versions.
+const char kMetricsOngoingLogsXml[] =
     "user_experience_metrics.ongoing_logs";
+const char kMetricsOngoingLogsProto[] =
+    "user_experience_metrics.ongoing_logs_as_protobufs";
 
 // Where profile specific metrics are placed.
 const char kProfileMetrics[] = "user_experience_metrics.profiles";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 14c6565..248a4b23 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -351,8 +351,10 @@
 extern const char kMetricsSessionID[];
 extern const char kMetricsClientIDTimestamp[];
 extern const char kMetricsReportingEnabled[];
-extern const char kMetricsInitialLogs[];
-extern const char kMetricsOngoingLogs[];
+extern const char kMetricsInitialLogsXml[];
+extern const char kMetricsInitialLogsProto[];
+extern const char kMetricsOngoingLogsXml[];
+extern const char kMetricsOngoingLogsProto[];
 
 extern const char kProfileLastUsed[];
 extern const char kProfilesLastActive[];
diff --git a/chrome_frame/metrics_service.cc b/chrome_frame/metrics_service.cc
index 01e3c50..622a892 100644
--- a/chrome_frame/metrics_service.cc
+++ b/chrome_frame/metrics_service.cc
@@ -419,6 +419,8 @@
   return user_permits_upload_;
 }
 
+// TODO(isherman): Update this to log to the protobuf server as well...
+// http://crbug.com/109817
 bool MetricsService::UploadData() {
   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
 
@@ -441,7 +443,7 @@
     ret = false;
   } else {
     HRESULT hr = ChromeFrameMetricsDataUploader::UploadDataHelper(
-        log_manager_.staged_log_text());
+        log_manager_.staged_log_text().xml);
     DCHECK(SUCCEEDED(hr));
   }
   log_manager_.DiscardStagedLog();