Persistent metrics files that have an embedded profile don't need to
be sent during startup.  Instead, send them as independent logs
once the browser is already running.

BUG=695880

Review-Url: https://codereview.chromium.org/2918533003
Cr-Commit-Position: refs/heads/master@{#478359}
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 9d24cd6d..699bee1e 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -177,6 +177,7 @@
     bool metrics_reporting_enabled,
     const base::FilePath& dir,
     base::StringPiece metrics_name,
+    metrics::FileMetricsProvider::SourceAssociation association,
     scoped_refptr<base::TaskRunner> task_runner,
     metrics::FileMetricsProvider* file_metrics_provider) {
   base::FilePath metrics_file;
@@ -188,7 +189,7 @@
     file_metrics_provider->RegisterSource(
         metrics_file,
         metrics::FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
-        metrics::FileMetricsProvider::ASSOCIATE_PREVIOUS_RUN, metrics_name);
+        association, metrics_name);
   } else {
     // When metrics reporting is not enabled, any existing file should be
     // deleted in order to preserve user privacy.
@@ -223,7 +224,8 @@
         metrics_reporting_enabled && (send_unreported == "yes");
     RegisterOrRemovePreviousRunMetricsFile(
         report_previous_persistent_histograms, user_data_dir,
-        ChromeMetricsServiceClient::kBrowserMetricsName, task_runner,
+        ChromeMetricsServiceClient::kBrowserMetricsName,
+        metrics::FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, task_runner,
         file_metrics_provider.get());
 
     // Register the Crashpad metrics files.
@@ -231,8 +233,10 @@
     // cleanly.
     RegisterOrRemovePreviousRunMetricsFile(
         metrics_reporting_enabled, user_data_dir,
-        kCrashpadHistogramAllocatorName, task_runner,
-        file_metrics_provider.get());
+        kCrashpadHistogramAllocatorName,
+        metrics::FileMetricsProvider::
+            ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+        task_runner, file_metrics_provider.get());
     if (metrics_reporting_enabled) {
       base::FilePath active_path;
       base::GlobalHistogramAllocator::ConstructFilePaths(
diff --git a/components/metrics/file_metrics_provider.cc b/components/metrics/file_metrics_provider.cc
index d9a6a04..61297c6e 100644
--- a/components/metrics/file_metrics_provider.cc
+++ b/components/metrics/file_metrics_provider.cc
@@ -20,6 +20,7 @@
 #include "base/time/time.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_service.h"
+#include "components/metrics/persistent_system_profile.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
@@ -70,6 +71,19 @@
   }
 };
 
+enum EmbeddedProfileResult : int {
+  EMBEDDED_PROFILE_ATTEMPT,
+  EMBEDDED_PROFILE_FOUND,
+  EMBEDDED_PROFILE_FALLBACK,
+  EMBEDDED_PROFILE_DROPPED,
+  EMBEDDED_PROFILE_ACTION_MAX
+};
+
+void RecordEmbeddedProfileResult(EmbeddedProfileResult result) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.EmbeddedProfileResult",
+                            result, EMBEDDED_PROFILE_ACTION_MAX);
+}
+
 void DeleteFileWhenPossible(const base::FilePath& path) {
   // Open (with delete) and then immediately close the file by going out of
   // scope. This is the only cross-platform safe way to delete a file that may
@@ -84,12 +98,16 @@
 // This structure stores all the information about the sources being monitored
 // and their current reporting state.
 struct FileMetricsProvider::SourceInfo {
-  SourceInfo(SourceType source_type) : type(source_type) {}
+  SourceInfo(SourceType source_type, SourceAssociation source_association)
+      : type(source_type), association(source_association) {}
   ~SourceInfo() {}
 
   // How to access this source (file/dir, atomic/active).
   const SourceType type;
 
+  // With what run this source is associated.
+  const SourceAssociation association;
+
   // Where on disk the directory is located. This will only be populated when
   // a directory is being monitored.
   base::FilePath directory;
@@ -136,7 +154,7 @@
   // Ensure that kSourceOptions has been filled for this type.
   DCHECK_GT(arraysize(kSourceOptions), static_cast<size_t>(type));
 
-  std::unique_ptr<SourceInfo> source(new SourceInfo(type));
+  std::unique_ptr<SourceInfo> source(new SourceInfo(type, source_association));
   source->prefs_key = prefs_key.as_string();
 
   switch (source->type) {
@@ -161,9 +179,11 @@
 
   switch (source_association) {
     case ASSOCIATE_CURRENT_RUN:
+    case ASSOCIATE_INTERNAL_PROFILE:
       sources_to_check_.push_back(std::move(source));
       break;
     case ASSOCIATE_PREVIOUS_RUN:
+    case ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN:
       DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type);
       sources_for_previous_run_.push_back(std::move(source));
       break;
@@ -250,6 +270,30 @@
 }
 
 // static
+void FileMetricsProvider::FinishedWithSource(SourceInfo* source,
+                                             AccessResult result) {
+  // Different source types require different post-processing.
+  switch (source->type) {
+    case SOURCE_HISTOGRAMS_ATOMIC_FILE:
+    case SOURCE_HISTOGRAMS_ATOMIC_DIR:
+      // Done with this file so delete the allocator and its owned file.
+      source->allocator.reset();
+      // Remove the file if has been recorded. This prevents them from
+      // accumulating or also being recorded by different instances of
+      // the browser.
+      if (result == ACCESS_RESULT_SUCCESS ||
+          result == ACCESS_RESULT_NOT_MODIFIED) {
+        DeleteFileWhenPossible(source->path);
+      }
+      break;
+    case SOURCE_HISTOGRAMS_ACTIVE_FILE:
+      // Keep the allocator open so it doesn't have to be re-mapped each
+      // time. This also allows the contents to be merged on-demand.
+      break;
+  }
+}
+
+// static
 void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner(
     SourceInfoList* sources) {
   // This method has all state information passed in |sources| and is intended
@@ -264,31 +308,19 @@
           "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX);
     }
 
+    // Metrics associated with internal profiles have to be fetched directly
+    // so just keep the mapping for use by the main thread.
+    if (source->association == ASSOCIATE_INTERNAL_PROFILE)
+      continue;
+
     // Mapping was successful. Merge it.
     if (result == ACCESS_RESULT_SUCCESS) {
       MergeHistogramDeltasFromSource(source.get());
       DCHECK(source->read_complete);
     }
 
-    // Different source types require different post-processing.
-    switch (source->type) {
-      case SOURCE_HISTOGRAMS_ATOMIC_FILE:
-      case SOURCE_HISTOGRAMS_ATOMIC_DIR:
-        // Done with this file so delete the allocator and its owned file.
-        source->allocator.reset();
-        // Remove the file if has been recorded. This prevents them from
-        // accumulating or also being recorded by different instances of
-        // the browser.
-        if (result == ACCESS_RESULT_SUCCESS ||
-            result == ACCESS_RESULT_NOT_MODIFIED) {
-          base::DeleteFile(source->path, /*recursive=*/false);
-        }
-        break;
-      case SOURCE_HISTOGRAMS_ACTIVE_FILE:
-        // Keep the allocator open so it doesn't have to be re-mapped each
-        // time. This also allows the contents to be merged on-demand.
-        break;
-    }
+    // All done with this source.
+    FinishedWithSource(source.get(), result);
   }
 }
 
@@ -297,8 +329,11 @@
 // static
 FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource(
     SourceInfo* source) {
-  DCHECK(!source->allocator);
+  // If source was read, clean up after it.
+  if (source->read_complete)
+    FinishedWithSource(source, ACCESS_RESULT_SUCCESS);
   source->read_complete = false;
+  DCHECK(!source->allocator);
 
   // If the source is a directory, look for files within it.
   if (!source->directory.empty() && !LocateNextFileInDirectory(source))
@@ -437,10 +472,16 @@
       RecordSourceAsRead(source);
       did_read = true;
     }
-    if (source->allocator)
-      sources_mapped_.splice(sources_mapped_.end(), *checked, temp);
-    else
+    if (source->allocator) {
+      if (source->association == ASSOCIATE_INTERNAL_PROFILE) {
+        sources_with_profile_.splice(sources_with_profile_.end(), *checked,
+                                     temp);
+      } else {
+        sources_mapped_.splice(sources_mapped_.end(), *checked, temp);
+      }
+    } else {
       sources_to_check_.splice(sources_to_check_.end(), *checked, temp);
+    }
   }
 
   // If a read was done, schedule another one immediately. In the case of a
@@ -487,6 +528,39 @@
   sources_for_previous_run_.clear();
 }
 
+bool FileMetricsProvider::ProvideIndependentMetrics(
+    SystemProfileProto* system_profile_proto,
+    base::HistogramSnapshotManager* snapshot_manager) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  while (!sources_with_profile_.empty()) {
+    SourceInfo* source = sources_with_profile_.begin()->get();
+    DCHECK(source->allocator);
+
+    bool success = false;
+    RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT);
+    if (PersistentSystemProfile::GetSystemProfile(
+            *source->allocator->memory_allocator(), system_profile_proto)) {
+      RecordHistogramSnapshotsFromSource(snapshot_manager, source);
+      success = true;
+      RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FOUND);
+    } else {
+      RecordEmbeddedProfileResult(EMBEDDED_PROFILE_DROPPED);
+    }
+
+    // Regardless of whether this source was successfully recorded, it is never
+    // read again.
+    source->read_complete = true;
+    RecordSourceAsRead(source);
+    sources_to_check_.splice(sources_to_check_.end(), sources_with_profile_,
+                             sources_with_profile_.begin());
+    if (success)
+      return true;
+  }
+
+  return false;
+}
+
 bool FileMetricsProvider::HasInitialStabilityMetrics() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
@@ -521,6 +595,21 @@
       RecordSourceAsRead(source);
       DeleteFileAsync(source->path);
       sources_for_previous_run_.erase(temp);
+      continue;
+    }
+
+    DCHECK(source->allocator);
+
+    // If the source should be associated with an existing internal profile,
+    // move it to |sources_with_profile_| for later upload.
+    if (source->association == ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN) {
+      if (PersistentSystemProfile::HasSystemProfile(
+              *source->allocator->memory_allocator())) {
+        RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT);
+        RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FALLBACK);
+        sources_with_profile_.splice(sources_with_profile_.end(),
+                                     sources_for_previous_run_, temp);
+      }
     }
   }
 
diff --git a/components/metrics/file_metrics_provider.h b/components/metrics/file_metrics_provider.h
index 43d4d30..a19577b 100644
--- a/components/metrics/file_metrics_provider.h
+++ b/components/metrics/file_metrics_provider.h
@@ -80,6 +80,19 @@
     // This is important when metrics are dumped as part of a crash of the
     // previous run. This can only be used with FILE_HISTOGRAMS_ATOMIC.
     ASSOCIATE_PREVIOUS_RUN,
+
+    // Associates the metrics in the file with the a profile embedded in the
+    // same file. The reporting will take place at a convenient time after
+    // startup when the browser is otherwise idle. If there is no embedded
+    // system profile, these metrics will be lost.
+    ASSOCIATE_INTERNAL_PROFILE,
+
+    // Like above but fall back to ASSOCIATE_PREVIOUS_RUN if there is no
+    // embedded profile. This has a small cost during startup as that is
+    // when previous-run metrics are sent so the file has be checked at
+    // that time even though actual transfer will be delayed if an
+    // embedded profile is found.
+    ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
   };
 
   FileMetricsProvider(const scoped_refptr<base::TaskRunner>& task_runner,
@@ -147,6 +160,9 @@
   // be internally updated to indicate the next file to be read.
   static bool LocateNextFileInDirectory(SourceInfo* source);
 
+  // Handles the completion of a source.
+  static void FinishedWithSource(SourceInfo* source, AccessResult result);
+
   // Checks a list of sources (on a task-runner allowed to do I/O) and merge
   // any data found within them.
   static void CheckAndMergeMetricSourcesOnTaskRunner(SourceInfoList* sources);
@@ -175,8 +191,11 @@
   // Updates the persistent state information to show a source as being read.
   void RecordSourceAsRead(SourceInfo* source);
 
-  // metrics::MetricsDataProvider:
+  // metrics::MetricsProvider:
   void OnDidCreateMetricsLog() override;
+  bool ProvideIndependentMetrics(
+      SystemProfileProto* system_profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager) override;
   bool HasInitialStabilityMetrics() override;
   void RecordInitialHistogramSnapshots(
       base::HistogramSnapshotManager* snapshot_manager) override;
@@ -193,6 +212,9 @@
   // A list of currently active sources to be merged when required.
   SourceInfoList sources_mapped_;
 
+  // A list of currently active sources to be merged when required.
+  SourceInfoList sources_with_profile_;
+
   // A list of sources for a previous run. These are held separately because
   // they are not subject to the periodic background checking that handles
   // metrics for the current run.
diff --git a/components/metrics/file_metrics_provider_unittest.cc b/components/metrics/file_metrics_provider_unittest.cc
index 9781cea..2b6fbdb 100644
--- a/components/metrics/file_metrics_provider_unittest.cc
+++ b/components/metrics/file_metrics_provider_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "components/metrics/file_metrics_provider.h"
 
+#include <functional>
+
 #include "base/files/file_util.h"
 #include "base/files/memory_mapped_file.h"
 #include "base/files/scoped_temp_dir.h"
@@ -20,6 +22,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/persistent_system_profile.h"
+#include "components/metrics/proto/system_profile.pb.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -118,6 +122,13 @@
     provider()->MergeHistogramDeltas();
   }
 
+  bool ProvideIndependentMetrics(
+      SystemProfileProto* profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager) {
+    return provider()->ProvideIndependentMetrics(profile_proto,
+                                                 snapshot_manager);
+  }
+
   void RecordInitialHistogramSnapshots(
       base::HistogramSnapshotManager* snapshot_manager) {
     provider()->RecordInitialHistogramSnapshots(snapshot_manager);
@@ -176,7 +187,10 @@
   }
 
   std::unique_ptr<base::PersistentHistogramAllocator>
-  CreateMetricsFileWithHistograms(int histogram_count) {
+  CreateMetricsFileWithHistograms(
+      int histogram_count,
+      const std::function<void(base::PersistentHistogramAllocator*)>&
+          callback) {
     // Get this first so it isn't created inside the persistent allocator.
     base::GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
 
@@ -188,10 +202,18 @@
 
     std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
         base::GlobalHistogramAllocator::ReleaseForTesting();
+    callback(histogram_allocator.get());
+
     WriteMetricsFile(metrics_file(), histogram_allocator.get());
     return histogram_allocator;
   }
 
+  std::unique_ptr<base::PersistentHistogramAllocator>
+  CreateMetricsFileWithHistograms(int histogram_count) {
+    return CreateMetricsFileWithHistograms(
+        histogram_count, [](base::PersistentHistogramAllocator* allocator) {});
+  }
+
   base::HistogramBase* GetCreatedHistogram(int index) {
     DCHECK_GT(kMaxCreateHistograms, index);
     return created_histograms_[index];
@@ -411,7 +433,7 @@
                              kMetricsName);
 
   // Record embedded snapshots via snapshot-manager.
-  HasInitialStabilityMetrics();
+  ASSERT_TRUE(HasInitialStabilityMetrics());
   RunTasks();
   {
     HistogramFlattenerDeltaRecorder flattener;
@@ -435,4 +457,144 @@
   EXPECT_TRUE(base::PathExists(metrics_file()));
 }
 
+TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithoutProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(2);
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName);
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    SystemProfileProto profile;
+
+    // A read of metrics with internal profiles should return nothing.
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(
+      2, [](base::PersistentHistogramAllocator* allocator) {
+        SystemProfileProto profile_proto;
+        SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial();
+        trial->set_name_id(123);
+        trial->set_group_id(456);
+
+        PersistentSystemProfile persistent_profile;
+        persistent_profile.RegisterPersistentAllocator(
+            allocator->memory_allocator());
+        persistent_profile.SetSystemProfile(profile_proto);
+      });
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName);
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return one result.
+    SystemProfileProto profile;
+    EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithoutProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(2);
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+      kMetricsName);
+
+  // Record embedded snapshots via snapshot-manager.
+  ASSERT_TRUE(HasInitialStabilityMetrics());
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return nothing.
+    SystemProfileProto profile;
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(
+      2, [](base::PersistentHistogramAllocator* allocator) {
+        SystemProfileProto profile_proto;
+        SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial();
+        trial->set_name_id(123);
+        trial->set_group_id(456);
+
+        PersistentSystemProfile persistent_profile;
+        persistent_profile.RegisterPersistentAllocator(
+            allocator->memory_allocator());
+        persistent_profile.SetSystemProfile(profile_proto);
+      });
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+      kMetricsName);
+
+  // Record embedded snapshots via snapshot-manager.
+  EXPECT_FALSE(HasInitialStabilityMetrics());
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return one result.
+    SystemProfileProto profile;
+    EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
 }  // namespace metrics
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
index 0523372..4d3123c 100644
--- a/components/metrics/metrics_log.cc
+++ b/components/metrics/metrics_log.cc
@@ -11,8 +11,11 @@
 
 #include "base/build_time.h"
 #include "base/cpu.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_samples.h"
+#include "base/metrics/histogram_snapshot_manager.h"
 #include "base/metrics/metrics_hashes.h"
 #include "base/sys_info.h"
 #include "base/time/time.h"
@@ -51,6 +54,28 @@
 
 namespace {
 
+// A simple class to write histogram data to a log.
+class IndependentFlattener : public base::HistogramFlattener {
+ public:
+  explicit IndependentFlattener(MetricsLog* log) : log_(log) {}
+
+  // base::HistogramFlattener:
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override {
+    log_->RecordHistogramDelta(histogram.histogram_name(), snapshot);
+  }
+  void InconsistencyDetected(
+      base::HistogramBase::Inconsistency problem) override {}
+  void UniqueInconsistencyDetected(
+      base::HistogramBase::Inconsistency problem) override {}
+  void InconsistencyDetectedInLoggedCount(int amount) override {}
+
+ private:
+  MetricsLog* const log_;
+
+  DISALLOW_COPY_AND_ASSIGN(IndependentFlattener);
+};
+
 // Any id less than 16 bytes is considered to be a testing id.
 bool IsTestingID(const std::string& id) {
   return id.size() < 16;
@@ -319,6 +344,15 @@
   return serialized_proto;
 }
 
+bool MetricsLog::LoadIndependentMetrics(MetricsProvider* metrics_provider) {
+  SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+  IndependentFlattener flattener(this);
+  base::HistogramSnapshotManager snapshot_manager(&flattener);
+
+  return metrics_provider->ProvideIndependentMetrics(system_profile,
+                                                     &snapshot_manager);
+}
+
 bool MetricsLog::LoadSavedEnvironmentFromPrefs(std::string* app_version) {
   DCHECK(app_version);
   app_version->clear();
diff --git a/components/metrics/metrics_log.h b/components/metrics/metrics_log.h
index 441cfbc..688a624 100644
--- a/components/metrics/metrics_log.h
+++ b/components/metrics/metrics_log.h
@@ -44,6 +44,7 @@
   enum LogType {
     INITIAL_STABILITY_LOG,  // The initial log containing stability stats.
     ONGOING_LOG,            // Subsequent logs in a session.
+    INDEPENDENT_LOG,        // An independent log from a previous session.
   };
 
   // Creates a new metrics log of the specified type.
@@ -105,6 +106,11 @@
       int64_t install_date,
       int64_t metrics_reporting_enabled_date);
 
+  // Loads a saved system profile and the associated metrics into the log.
+  // Returns true on success. Keep calling it with fresh logs until it returns
+  // false.
+  bool LoadIndependentMetrics(MetricsProvider* metrics_provider);
+
   // Loads the environment proto that was saved by the last RecordEnvironment()
   // call from prefs. On success, returns true and |app_version| contains the
   // recovered version. Otherwise (if there was no saved environment in prefs
diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc
index 8b1ec2a7..30cbe6c 100644
--- a/components/metrics/metrics_log_store.cc
+++ b/components/metrics/metrics_log_store.cc
@@ -73,6 +73,7 @@
       initial_log_queue_.StoreLog(log_data);
       break;
     case MetricsLog::ONGOING_LOG:
+    case MetricsLog::INDEPENDENT_LOG:
       ongoing_log_queue_.StoreLog(log_data);
       break;
   }
diff --git a/components/metrics/metrics_provider.cc b/components/metrics/metrics_provider.cc
index fd864e41..c5ca585 100644
--- a/components/metrics/metrics_provider.cc
+++ b/components/metrics/metrics_provider.cc
@@ -27,6 +27,12 @@
 void MetricsProvider::OnAppEnterBackground() {
 }
 
+bool MetricsProvider::ProvideIndependentMetrics(
+    SystemProfileProto* system_profile_proto,
+    base::HistogramSnapshotManager* snapshot_manager) {
+  return false;
+}
+
 void MetricsProvider::ProvideSystemProfileMetrics(
     SystemProfileProto* system_profile_proto) {
 }
diff --git a/components/metrics/metrics_provider.h b/components/metrics/metrics_provider.h
index 54c0a89..a867ef4 100644
--- a/components/metrics/metrics_provider.h
+++ b/components/metrics/metrics_provider.h
@@ -42,6 +42,14 @@
   // further notification after this callback.
   virtual void OnAppEnterBackground();
 
+  // Provides a complete and independent system profile + metrics for uploading.
+  // Any histograms added to the |snapshot_manager| will also be included. A
+  // return of false indicates there are none. Will be called repeatedly until
+  // there is nothing else.
+  virtual bool ProvideIndependentMetrics(
+      SystemProfileProto* system_profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager);
+
   // Provides additional metrics into the system profile.
   virtual void ProvideSystemProfileMetrics(
       SystemProfileProto* system_profile_proto);
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index f489d16..6a44e35 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -625,6 +625,12 @@
         FROM_HERE, base::Bind(&MetricsService::StartInitTask,
                               self_ptr_factory_.GetWeakPtr()),
         base::TimeDelta::FromSeconds(kInitializationDelaySeconds));
+
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&MetricsService::PrepareProviderMetricsTask,
+                   self_ptr_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(2 * kInitializationDelaySeconds));
   }
 }
 
@@ -971,6 +977,37 @@
     provider->RecordInitialHistogramSnapshots(&histogram_snapshot_manager_);
 }
 
+bool MetricsService::PrepareProviderMetricsLog() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Create a new log. This will have some defaut values injected in it but
+  // those will be overwritten when an embedded profile is extracted.
+  std::unique_ptr<MetricsLog> log = CreateLog(MetricsLog::INDEPENDENT_LOG);
+
+  for (auto& provider : metrics_providers_) {
+    if (log->LoadIndependentMetrics(provider.get())) {
+      log_manager_.PauseCurrentLog();
+      log_manager_.BeginLoggingWithLog(std::move(log));
+      log_manager_.FinishCurrentLog(log_store());
+      log_manager_.ResumePausedLog();
+      return true;
+    }
+  }
+  return false;
+}
+
+void MetricsService::PrepareProviderMetricsTask() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  bool found = PrepareProviderMetricsLog();
+  base::TimeDelta next_check = found ? base::TimeDelta::FromSeconds(5)
+                                     : base::TimeDelta::FromMinutes(15);
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&MetricsService::PrepareProviderMetricsTask,
+                 self_ptr_factory_.GetWeakPtr()),
+      next_check);
+}
+
 void MetricsService::LogCleanShutdown(bool end_completed) {
   DCHECK(thread_checker_.CalledOnValidThread());
   // Redundant setting to assure that we always reset this value at shutdown
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
index f95fd6d..102ab72 100644
--- a/components/metrics/metrics_service.h
+++ b/components/metrics/metrics_service.h
@@ -353,6 +353,15 @@
   // i.e., histograms with the |kUmaStabilityHistogramFlag| flag set.
   void RecordCurrentStabilityHistograms();
 
+  // Record a single independent profile and assocatied histogram from
+  // metrics providers. If this returns true, one was found and there may
+  // be more.
+  bool PrepareProviderMetricsLog();
+
+  // Records one independent histogram log and then reschedules itself to
+  // check for others. The interval is so as to not adversely impact the UI.
+  void PrepareProviderMetricsTask();
+
   // Sub-service for uploading logs.
   MetricsReportingService reporting_service_;
 
diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc
index 747e38b..e426789 100644
--- a/components/metrics/metrics_service_unittest.cc
+++ b/components/metrics/metrics_service_unittest.cc
@@ -491,45 +491,46 @@
   service.Start();
   // Rotation loop should create a log and mark state as idle.
   // Upload loop should start upload or be restarted.
+  // The independent-metrics upload job will be started and always be a task.
   task_runner_->RunPendingTasks();
   // Rotation loop should terminated due to being idle.
   // Upload loop should start uploading if it isn't already.
   task_runner_->RunPendingTasks();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(0U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
   service.OnApplicationNotIdle();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
   // Log generation should be suppressed due to unsent log.
   // Idle state should not be reset.
   task_runner_->RunPendingTasks();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
   // Make sure idle state was not reset.
   task_runner_->RunPendingTasks();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
   // Upload should not be rescheduled, since there are no other logs.
   client.uploader()->CompleteUpload(200);
   EXPECT_FALSE(client.uploader()->is_uploading());
-  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
   // Running should generate a log, restart upload loop, and mark idle.
   task_runner_->RunPendingTasks();
   EXPECT_FALSE(client.uploader()->is_uploading());
-  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(3U, task_runner_->NumPendingTasks());
   // Upload should start, and rotation loop should idle out.
   task_runner_->RunPendingTasks();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(0U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
   // Uploader should reschedule when there is another log available.
   service.PushExternalLog("Blah");
   client.uploader()->CompleteUpload(200);
   EXPECT_FALSE(client.uploader()->is_uploading());
-  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
   // Upload should start.
   task_runner_->RunPendingTasks();
   EXPECT_TRUE(client.uploader()->is_uploading());
-  EXPECT_EQ(0U, task_runner_->NumPendingTasks());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
 }
 
 TEST_F(MetricsServiceTest, GetSyntheticFieldTrialActiveGroups) {
diff --git a/components/metrics/persistent_system_profile.cc b/components/metrics/persistent_system_profile.cc
index 526b1407..dc4e7eb 100644
--- a/components/metrics/persistent_system_profile.cc
+++ b/components/metrics/persistent_system_profile.cc
@@ -101,6 +101,22 @@
   return true;
 }
 
+bool PersistentSystemProfile::RecordAllocator::HasMoreData() const {
+  if (alloc_reference_ == 0 && !NextSegment())
+    return false;
+
+  char* block =
+      allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
+                                   base::PersistentMemoryAllocator::kSizeAny);
+  if (!block)
+    return false;
+
+  RecordHeader header;
+  header.as_atomic = base::subtle::Acquire_Load(
+      reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_));
+  return header.as_parts.type != kUnusedSpace;
+}
+
 bool PersistentSystemProfile::RecordAllocator::Read(RecordType* type,
                                                     std::string* record) const {
   *type = kUnusedSpace;
@@ -276,6 +292,21 @@
   }
 }
 
+void PersistentSystemProfile::SetSystemProfile(
+    const SystemProfileProto& profile) {
+  std::string serialized_profile;
+  if (!profile.SerializeToString(&serialized_profile))
+    return;
+  SetSystemProfile(serialized_profile);
+}
+
+// static
+bool PersistentSystemProfile::HasSystemProfile(
+    const base::PersistentMemoryAllocator& memory_allocator) {
+  const RecordAllocator records(&memory_allocator);
+  return records.HasMoreData();
+}
+
 // static
 bool PersistentSystemProfile::GetSystemProfile(
     const base::PersistentMemoryAllocator& memory_allocator,
diff --git a/components/metrics/persistent_system_profile.h b/components/metrics/persistent_system_profile.h
index 30d971f6..1775dde 100644
--- a/components/metrics/persistent_system_profile.h
+++ b/components/metrics/persistent_system_profile.h
@@ -30,8 +30,14 @@
   void DeregisterPersistentAllocator(
       base::PersistentMemoryAllocator* memory_allocator);
 
-  // Stores a complete system profile.
+  // Stores a complete system profile. Use the version taking the serialized
+  // version if available to avoid multiple serialization actions.
   void SetSystemProfile(const std::string& serialized_profile);
+  void SetSystemProfile(const SystemProfileProto& profile);
+
+  // Tests if a persistent memory allocator contains an system profile.
+  static bool HasSystemProfile(
+      const base::PersistentMemoryAllocator& memory_allocator);
 
   // Retrieves the system profile from a persistent memory allocator. Returns
   // true if a profile was successfully retrieved.
@@ -65,6 +71,7 @@
 
     // Read a record from the allocator. Do not mix this with "write" calls;
     // it's one or the other.
+    bool HasMoreData() const;
     bool Read(RecordType* type, std::string* record) const;
 
     base::PersistentMemoryAllocator* allocator() { return allocator_; }
diff --git a/components/metrics/persistent_system_profile_unittest.cc b/components/metrics/persistent_system_profile_unittest.cc
index 93f297a..5ca4e898 100644
--- a/components/metrics/persistent_system_profile_unittest.cc
+++ b/components/metrics/persistent_system_profile_unittest.cc
@@ -91,11 +91,10 @@
   trial->set_name_id(123);
   trial->set_group_id(456);
 
-  std::string serialized_proto;
-  ASSERT_TRUE(proto1.SerializeToString(&serialized_proto));
-  persistent_profile()->SetSystemProfile(serialized_proto);
+  persistent_profile()->SetSystemProfile(proto1);
 
   SystemProfileProto proto2;
+  ASSERT_TRUE(PersistentSystemProfile::HasSystemProfile(*memory_allocator()));
   ASSERT_TRUE(
       PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2));
   ASSERT_EQ(1, proto2.field_trial_size());
@@ -108,8 +107,7 @@
   trial->set_name_id(78);
   trial->set_group_id(90);
 
-  ASSERT_TRUE(proto1.SerializeToString(&serialized_proto));
-  persistent_profile()->SetSystemProfile(serialized_proto);
+  persistent_profile()->SetSystemProfile(proto1);
 
   ASSERT_TRUE(
       PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2));
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9fd1f81..13a7262 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -16312,6 +16312,15 @@
   <int value="7" label="File contents internally deleted."/>
 </enum>
 
+<enum name="FileMetricsProviderEmbeddedProfileResult" type="int">
+  <int value="0" label="File was accessed."/>
+  <int value="1" label="File had embedded profile."/>
+  <int value="2"
+      label="File used fallback because no embedded profile was found."/>
+  <int value="3"
+      label="File was dropped because no embedded profile was found."/>
+</enum>
+
 <enum name="FileReaderSyncWorkerType" type="int">
   <int value="0" label="Other"/>
   <int value="1" label="Dedicated Worker"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 3064958..f3310ae 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -78703,6 +78703,15 @@
   </summary>
 </histogram>
 
+<histogram name="UMA.FileMetricsProvider.EmbeddedProfileResult"
+    enum="FileMetricsProviderEmbeddedProfileResult">
+  <owner>asvitkine@chromium.org</owner>
+  <owner>bcwhite@chromium.org</owner>
+  <summary>
+    Records attempts to upload metrics from files with embedded system profiles.
+  </summary>
+</histogram>
+
 <histogram name="UMA.FileMetricsProvider.InitialAccessResult"
     enum="FileMetricsProviderAccessResult">
   <owner>asvitkine@chromium.org</owner>