Bug 1453591 - Add persistence support for GeckoView in the Telemetry core. r?chutten,janerik,gfritzsche,froydnj,bz draft
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Mon, 16 Apr 2018 11:15:48 +0200
changeset 794881 078911ee8e813e1cb761043f76710ba956014108
parent 794866 c96b4323d7b8149d7737723e1a4937447cb46c18
child 794882 6a6e8c6bdb2df73f8076ddd540a92c2305e695cf
push id109810
push userbmo:alessio.placitelli@gmail.com
push dateMon, 14 May 2018 19:22:20 +0000
reviewerschutten, janerik, gfritzsche, froydnj, bz
bugs1453591
milestone62.0a1
Bug 1453591 - Add persistence support for GeckoView in the Telemetry core. r?chutten,janerik,gfritzsche,froydnj,bz This implements the persistence timer and the logic to write measurements to a persistence file off-the-main thread using JSONWriter. The code to load persisted measurements off the disk uses the builtin JSON parsing functions from JS (as we cannot use jsoncpp). It additionally exposes the ClearProbes method to clear the probe storage and trigger clearing the persisted data (GeckoView only). MozReview-Commit-ID: 94FoZac47Cw
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.h
toolkit/components/telemetry/moz.build
toolkit/components/telemetry/nsITelemetry.idl
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -90,23 +90,29 @@
 #include "nsProxyRelease.h"
 #include "HangReports.h"
 
 #if defined(MOZ_GECKO_PROFILER)
 #include "shared-libraries.h"
 #include "KeyedStackCapturer.h"
 #endif // MOZ_GECKO_PROFILER
 
+#if defined(MOZ_WIDGET_ANDROID)
+#include "geckoview/TelemetryGeckoViewPersistence.h"
+#endif
+
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 using Telemetry::Common::AutoHashtable;
 using Telemetry::Common::ToJSString;
+using Telemetry::Common::GetCurrentProduct;
 using Telemetry::Common::SetCurrentProduct;
+using Telemetry::Common::SupportedProduct;
 using mozilla::dom::Promise;
 using mozilla::dom::AutoJSAPI;
 using mozilla::Telemetry::HangReports;
 using mozilla::Telemetry::CombinedStacks;
 using mozilla::Telemetry::ComputeAnnotationsKey;
 using mozilla::Telemetry::TelemetryIOInterposeObserver;
 
 #if defined(MOZ_GECKO_PROFILER)
@@ -1271,32 +1277,47 @@ TelemetryImpl::CreateTelemetryInstance()
   nsCOMPtr<nsITelemetry> ret = sTelemetry;
 
   sTelemetry->mCanRecordBase = useTelemetry;
   sTelemetry->mCanRecordExtended = useTelemetry;
 
   sTelemetry->InitMemoryReporter();
   InitHistogramRecordingEnabled(); // requires sTelemetry to exist
 
+#if defined MOZ_WIDGET_ANDROID
+  // We only want to add persistence for GeckoView, but both
+  // GV and Fennec are on Android. So just init persistence if this
+  // is Android but not Fennec.
+  if (GetCurrentProduct() == SupportedProduct::Geckoview) {
+    TelemetryGeckoViewPersistence::InitPersistence();
+  }
+#endif
+
   return ret.forget();
 }
 
 void
 TelemetryImpl::ShutdownTelemetry()
 {
   // No point in collecting IO beyond this point
   ClearIOReporting();
   NS_IF_RELEASE(sTelemetry);
 
   // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global states,
   // so as to release any heap storage that would otherwise be kept alive by it.
   TelemetryHistogram::DeInitializeGlobalState();
   TelemetryScalar::DeInitializeGlobalState();
   TelemetryEvent::DeInitializeGlobalState();
   TelemetryIPCAccumulator::DeInitializeGlobalState();
+
+#if defined(MOZ_WIDGET_ANDROID)
+  if (GetCurrentProduct() == SupportedProduct::Geckoview) {
+    TelemetryGeckoViewPersistence::DeInitPersistence();
+  }
+#endif
 }
 
 void
 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
                             SanitizedState state)
 {
   AutoHashtable<SlowSQLEntryType>* slowSQLMap = nullptr;
   if (state == Sanitized)
@@ -1832,16 +1853,35 @@ TelemetryImpl::ResetCurrentProduct()
   SetCurrentProduct();
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
 NS_IMETHODIMP
+TelemetryImpl::ClearProbes()
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  // We only support this in GeckoView.
+  if (GetCurrentProduct() != SupportedProduct::Geckoview) {
+    MOZ_ASSERT(false, "ClearProbes is only supported on GeckoView");
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: supporting clear for histograms will come from bug 1457127.
+  TelemetryScalar::ClearScalars();
+  TelemetryGeckoViewPersistence::ClearPersistenceData();
+  return NS_OK;
+#else
+  return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
 TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled)
 {
   TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::FlushBatchedChildTelemetry()
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TelemetryGeckoViewPersistence.h"
+
+#include "jsapi.h"
+#include "mozilla/JSONWriter.h"
+#include "mozilla/Path.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/SystemGroup.h"
+#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsITimer.h"
+#include "nsLocalFile.h"
+#include "nsNetUtil.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+#include "prio.h"
+#include "TelemetryScalar.h"
+#include "xpcpublic.h"
+
+using mozilla::MakeScopeExit;
+using mozilla::Preferences;
+using mozilla::StaticRefPtr;
+using mozilla::SystemGroup;
+using mozilla::TaskCategory;
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::SimpleGlobalObject;
+
+using PathChar = mozilla::filesystem::Path::value_type;
+using PathCharPtr = const PathChar*;
+
+// Enable logging by default on Debug builds.
+#ifdef DEBUG
+// If we're building for Android, use the provided logging facility.
+#ifdef MOZ_WIDGET_ANDROID
+#include <android/log.h>
+#define ANDROID_LOG(fmt, ...) \
+  __android_log_print(ANDROID_LOG_DEBUG, "Telemetry", fmt, ##__VA_ARGS__)
+#else
+// If we're building for other platforms (e.g. for running test coverage), try
+// to print something anyway.
+#define ANDROID_LOG(...) printf_stderr("\n**** TELEMETRY: " __VA_ARGS__)
+#endif // MOZ_WIDGET_ANDROID
+#else
+// No-op on Release builds.
+#define ANDROID_LOG(...)
+#endif // DEBUG
+
+// The Gecko runtime can be killed at anytime. Moreover, we can
+// have very short lived sessions. The persistence timeout governs
+// how frequently measurements are saved to disk.
+const uint32_t kDefaultPersistenceTimeoutMs = 60 * 1000; // 60s
+
+// The name of the persistence file used for saving the
+// measurements.
+const char16_t kPersistenceFileName[] = u"gv_measurements.json";
+
+// The timer used for persisting measurements data.
+nsITimer* gPersistenceTimer;
+// The worker thread to perform persistence.
+StaticRefPtr<nsIThread> gPersistenceThread;
+
+namespace {
+
+void PersistenceThreadPersist();
+
+/**
++ * The helper class used by mozilla::JSONWriter to
++ * serialize the JSON structure to a file.
++ */
+class StreamingJSONWriter : public mozilla::JSONWriteFunc
+{
+public:
+  nsresult Open(nsCOMPtr<nsIFile> aOutFile)
+  {
+    MOZ_ASSERT(!mStream, "Open must not be called twice");
+    nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(mStream), aOutFile);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
+  }
+
+  nsresult Close()
+  {
+    MOZ_ASSERT(mStream, "Close must be called on an already opened stream");
+    // We don't need to care too much about checking if count matches
+    // the length of aData: Finish() will do that for us and fail if
+    // Write did not persist all the data or mStream->Close() failed.
+    // Note that |nsISafeOutputStream| will write to a temp file and only
+    // overwrite the destination if no error was reported.
+    nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(mStream);
+    MOZ_ASSERT(safeStream);
+    return safeStream->Finish();
+  }
+
+  void Write(const char* aStr) override
+  {
+    uint32_t count;
+    mozilla::Unused << mStream->Write(aStr, strlen(aStr), &count);
+  }
+
+private:
+  nsCOMPtr<nsIOutputStream> mStream;
+};
+
+/**
+ * Get the path to the Android Data dir.
+ *
+ * @param {nsTString<PathChar>} aOutDir - the variable holding the path.
+ * @return {nsresult} NS_OK if the data dir path was found, a failure value otherwise.
+ */
+nsresult
+GetAndroidDataDir(nsTString<PathChar>& aOutDir)
+{
+  // This relies on the Java environment to set the location of the
+  // cache directory. If that happens, the following variable is set.
+  // This should always be the case.
+  const char *dataDir = PR_GetEnv("MOZ_ANDROID_DATA_DIR");
+  if (!dataDir || !*dataDir) {
+    ANDROID_LOG("GetAndroidDataDir - Cannot find the data directory in the environment.");
+    return NS_ERROR_FAILURE;
+  }
+
+  aOutDir.AssignASCII(dataDir);
+  return NS_OK;
+}
+
+/**
+ * Get the path to the persistence file in the Android Data dir.
+ *
+ * @param {nsCOMPtr<nsIFile>} aOutFile - the nsIFile pointer holding the file info.
+ * @return {nsresult} NS_OK if the persistence file was found, a failure value otherwise.
+ */
+nsresult
+GetPersistenceFile(nsCOMPtr<nsIFile>& aOutFile)
+{
+  nsTString<PathChar> dataDir;
+  nsresult rv = GetAndroidDataDir(dataDir);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Append the extension to the filename.
+  nsAutoString fileName;
+  fileName.Assign(kPersistenceFileName);
+
+  aOutFile = new nsLocalFile(dataDir);
+  aOutFile->Append(fileName);
+  ANDROID_LOG("GetPersistenceFile -  %s", aOutFile->HumanReadablePath().get());
+  return NS_OK;
+}
+
+/**
+ * Read and parses JSON content from a file.
+ *
+ * @param {nsCOMPtr<nsIFile>} aFile - the nsIFile handle to the file.
+ * @param {nsACString} fileContent - the content of the file.
+ * @return {nsresult} NS_OK if the file was correctly read, an error code otherwise.
+ */
+nsresult
+ReadFromFile(const nsCOMPtr<nsIFile>& aFile, nsACString& fileContent)
+{
+  int64_t fileSize = 0;
+  nsresult rv = aFile->GetFileSize(&fileSize);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIInputStream> inStream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream),
+                                  aFile,
+                                  PR_RDONLY);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Make sure to close the stream.
+  auto scopedStreamClose = MakeScopeExit([inStream] { inStream->Close(); });
+
+  rv = NS_ReadInputStreamToString(inStream, fileContent, fileSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+/**
+ * Arms the persistence timer and instructs to run the persistence
+ * task off the main thread.
+ */
+void
+MainThreadArmPersistenceTimer()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ANDROID_LOG("MainThreadArmPersistenceTimer");
+
+  // We won't have a persistence timer the first time this runs, so take
+  // care of that.
+  if (!gPersistenceTimer) {
+    gPersistenceTimer =
+      NS_NewTimer(SystemGroup::EventTargetFor(TaskCategory::Other)).take();
+    if (!gPersistenceTimer) {
+      ANDROID_LOG("MainThreadArmPersistenceTimer - Timer creation failed.");
+      return;
+    }
+  }
+
+  // Define the callback for the persistence timer: it will dispatch the persistence
+  // task off the main thread. Once finished, it will trigger the timer again.
+  nsTimerCallbackFunc timerCallback = [](nsITimer* aTimer, void* aClosure) {
+    gPersistenceThread->Dispatch(NS_NewRunnableFunction("PersistenceThreadPersist",
+      []() -> void { ::PersistenceThreadPersist(); }));
+  };
+
+  uint32_t timeout = Preferences::GetUint("toolkit.telemetry.geckoPersistenceTimeout",
+                                          kDefaultPersistenceTimeoutMs);
+
+  // Schedule the timer to automatically run and reschedule
+  // every |kPersistenceTimeoutMs|.
+  gPersistenceTimer->InitWithNamedFuncCallback(timerCallback,
+                                               nullptr,
+                                               timeout,
+                                               nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+                                               "TelemetryGeckoViewPersistence::Persist");
+}
+
+/**
+ * Parse the string data into a JSON structure, using
+ * the native JS JSON parser.
+ */
+void
+MainThreadParsePersistedProbes(const nsACString& aProbeData)
+{
+  // We're required to run on the main thread since we're using JS.
+  MOZ_ASSERT(NS_IsMainThread());
+  ANDROID_LOG("MainThreadParsePersistedProbes");
+
+  // We need a JS context to run the parsing stuff in.
+  JSObject* cleanGlobal =
+    SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail);
+  if (!cleanGlobal) {
+    ANDROID_LOG("MainThreadParsePersistedProbes - Failed to create a JS global object");
+    return;
+  }
+
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(cleanGlobal))) {
+    ANDROID_LOG("MainThreadParsePersistedProbes - Failed to get JS API");
+    return;
+  }
+
+  // Parse the JSON using the JS API.
+  JS::RootedValue data(jsapi.cx());
+  NS_ConvertUTF8toUTF16 utf16Content(aProbeData);
+  if (!JS_ParseJSON(jsapi.cx(), utf16Content.BeginReading(), utf16Content.Length(), &data)) {
+    ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse the persisted JSON");
+    return;
+  }
+}
+
+/**
+ * The persistence worker function, meant to be run off the main thread.
+ */
+void
+PersistenceThreadPersist()
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process.");
+  MOZ_ASSERT(!NS_IsMainThread(), "This function must be called off the main thread.");
+  ANDROID_LOG("PersistenceThreadPersist");
+
+  // If the function completes or fails, make sure to spin up the persistence timer again.
+  auto scopedArmTimer = MakeScopeExit([&] {
+    NS_DispatchToMainThread(
+      NS_NewRunnableFunction("MainThreadArmPersistenceTimer", []() -> void {
+        MainThreadArmPersistenceTimer();
+      }));
+  });
+
+  nsCOMPtr<nsIFile> persistenceFile;
+  if (NS_FAILED(GetPersistenceFile(persistenceFile))) {
+    ANDROID_LOG("PersistenceThreadPersist - Failed to get the persistence file.");
+    return;
+  }
+
+  // Open the persistence file.
+  mozilla::UniquePtr<StreamingJSONWriter> jsonWriter =
+    mozilla::MakeUnique<StreamingJSONWriter>();
+
+  if (!jsonWriter || NS_FAILED(jsonWriter->Open(persistenceFile))) {
+    ANDROID_LOG("PersistenceThreadPersist - There was an error opening the persistence file.");
+    return;
+  }
+
+  // Build the JSON structure: give up the ownership of jsonWriter.
+  mozilla::JSONWriter w(mozilla::Move(jsonWriter));
+  w.Start();
+
+  // End the building process.
+  w.End();
+
+  // Android can kill us while we are writing to disk and, if that happens,
+  // we end up with a corrupted json overwriting the old session data.
+  // Luckily, |StreamingJSONWriter::Close| is smart enough to write to a
+  // temporary file and only overwrite the original file if nothing bad happened.
+  nsresult rv = static_cast<StreamingJSONWriter*>(w.WriteFunc())->Close();
+  if (NS_FAILED(rv)) {
+    ANDROID_LOG("PersistenceThreadPersist - There was an error writing to the persistence file.");
+    return;
+  }
+}
+
+/**
+ * This function loads the persisted metrics from a JSON file
+ * and adds them to the related storage. After it completes,
+ * it spins up the persistence timer.
+ *
+ * Please note that this function is meant to be run off the
+ * main-thread.
+ */
+void
+PersistenceThreadLoadData()
+{
+  MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process.");
+  MOZ_ASSERT(!NS_IsMainThread(), "We must perform I/O off the main thread.");
+  ANDROID_LOG("PersistenceThreadLoadData");
+
+  // If the function completes or fails, make sure to spin up the persistence timer.
+  nsAutoCString fileContent;
+  auto scopedArmTimer = MakeScopeExit([&] {
+    NS_DispatchToMainThread(
+      NS_NewRunnableFunction("MainThreadArmPersistenceTimer", [fileContent]() -> void {
+        // Try to parse the probes if the file was not empty.
+        if (!fileContent.IsEmpty()) {
+          MainThreadParsePersistedProbes(fileContent);
+        }
+        // Arm the timer.
+        MainThreadArmPersistenceTimer();
+        // Notify that we're good to take snapshots!
+      }));
+  });
+
+  // Attempt to load the persistence file. This could fail if we're not able
+  // to allocate enough memory for the content. See bug 1460911.
+  nsCOMPtr<nsIFile> persistenceFile;
+  if (NS_FAILED(GetPersistenceFile(persistenceFile))
+      || NS_FAILED(ReadFromFile(persistenceFile, fileContent))) {
+    ANDROID_LOG("PersistenceThreadLoadData - Failed to load cache file at %s",
+                persistenceFile->HumanReadablePath().get());
+    return;
+  }
+}
+
+} // anonymous namespace
+
+void
+TelemetryGeckoViewPersistence::InitPersistence()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (gPersistenceThread) {
+    ANDROID_LOG("Init must only be called once.");
+    return;
+  }
+
+  // Only register the persistence timer in the parent process in
+  // order to persist data for all the processes.
+  if (!XRE_IsParentProcess()) {
+    ANDROID_LOG("InitPersistence - Bailing out on child process.");
+    return;
+  }
+
+  ANDROID_LOG("InitPersistence");
+
+  // Spawn a new thread for handling GeckoView Telemetry persistence I/O.
+  // We just spawn it once and re-use it later.
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv =
+    NS_NewNamedThread("TelemetryGVIO", getter_AddRefs(thread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ANDROID_LOG("InitPersistence -  Failed to instantiate the worker thread.");
+    return;
+  }
+
+  gPersistenceThread = thread.forget();
+
+  // Trigger the loading of the persistence data. After the function
+  // completes it will automatically arm the persistence timer.
+  gPersistenceThread->Dispatch(
+    NS_NewRunnableFunction("PersistenceThreadLoadData", &PersistenceThreadLoadData));
+}
+
+void
+TelemetryGeckoViewPersistence::DeInitPersistence()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Bail out if this is not the parent process.
+  if (!XRE_IsParentProcess()) {
+    ANDROID_LOG("DeInitPersistence - Bailing out.");
+    return;
+  }
+
+  // Even though we need to implement this function, it might end up
+  // not being called: Android might kill us without notice to reclaim
+  // our memory in case some other foreground application needs it.
+  ANDROID_LOG("DeInitPersistence");
+
+  if (gPersistenceThread) {
+    gPersistenceThread->Shutdown();
+    gPersistenceThread = nullptr;
+  }
+
+  if (gPersistenceTimer) {
+    // Always make sure the timer is canceled.
+    MOZ_ALWAYS_SUCCEEDS(gPersistenceTimer->Cancel());
+    NS_RELEASE(gPersistenceTimer);
+  }
+}
+
+void
+TelemetryGeckoViewPersistence::ClearPersistenceData()
+{
+  // This can be run on any thread, as we just dispatch the persistence
+  // task to the persistence thread.
+  MOZ_ASSERT(gPersistenceThread);
+
+  ANDROID_LOG("ClearPersistenceData");
+
+  // Trigger clearing the persisted measurements off the main thread.
+  gPersistenceThread->Dispatch(NS_NewRunnableFunction("ClearPersistedData",
+    []() -> void {
+      nsCOMPtr<nsIFile> persistenceFile;
+      if (NS_FAILED(GetPersistenceFile(persistenceFile)) ||
+          NS_FAILED(persistenceFile->Remove(false))) {
+        ANDROID_LOG("ClearPersistenceData - Failed to remove the persistence file.");
+        return;
+      }
+    }));
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef GeckoViewTelemetryPersistence_h__
+#define GeckoViewTelemetryPersistence_h__
+
+namespace TelemetryGeckoViewPersistence {
+
+/**
+ * Initializes the GeckoView persistence.
+ * This loads any measure that was previously persisted and then kicks
+ * off the persistence timer that regularly serializes telemetry measurements
+ * to the disk (off the main thread).
+ *
+ * Note: while this code should only be used in GeckoView, it's also
+ * compiled on other platforms for test-coverage.
+ */
+void InitPersistence();
+
+/**
+ * Shuts down the GeckoView persistence.
+ */
+void DeInitPersistence();
+
+/**
+ * Clears any GeckoView persisted data.
+ * This physically deletes persisted data files.
+ */
+void ClearPersistenceData();
+
+} // namespace TelemetryGeckoViewPersistence
+
+#endif // GeckoViewTelemetryPersistence_h__
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -193,14 +193,18 @@ processes_data = GENERATED_FILES['Teleme
 processes_data.script = 'gen_process_data.py'
 processes_data.inputs = processes_files
 
 # Add support for GeckoView: please note that building GeckoView
 # implies having an Android build. The packaging step decides
 # which files to include. As a consequence, we can simply only
 # include the GeckoView files on all Android builds.
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+    SOURCES += [
+        'geckoview/TelemetryGeckoViewPersistence.cpp'
+    ]
+
     EXTRA_JS_MODULES += [
         'geckoview/GeckoViewTelemetryController.jsm',
     ]
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Telemetry')
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -567,9 +567,21 @@ interface nsITelemetry : nsISupports
    */
   void clearEvents();
 
   /**
    * Reset the current product. This is intended to be only used in Android tests.
    * It will fail on Desktop.
    */
   void resetCurrentProduct();
+
+  /**
+   * Reset the storage for all the collection primitives so that GeckoView
+   * can issue a single Clear signal for histograms, scalars, events, ...
+   *
+   * This is needed for supporting the current implementation of GeckoView
+   * measurement persistence: all the measurements are stored in a single file and
+   * they can't be cleared independently.
+   *
+   * Please note that this is only intended to be used by GeckoViewTelemetryController.
+   */
+  void clearProbes();
 };