--- a/toolkit/components/telemetry/geckoview/GeckoViewTelemetryPersistence.cpp
+++ b/toolkit/components/telemetry/geckoview/GeckoViewTelemetryPersistence.cpp
@@ -9,97 +9,186 @@
#include "FennecJNIWrappers.h"
#include "json/json.h"
#include "nsIFile.h"
#include "nsITimer.h"
#include "nsDirectoryServiceDefs.h"
#include "prenv.h"
#include "TelemetryScalar.h"
#include "mozilla/FStream.h"
+#include "mozilla/ScopeExit.h"
#include "mozilla/SystemGroup.h"
#include "nsXULAppAPI.h"
// #define ANDROID_LOG(...)
#define ANDROID_LOG(...) printf_stderr("\n**** TELEMETRY: " __VA_ARGS__)
namespace TelemetryGeckoViewPersistence = mozilla::TelemetryGeckoViewPersistence;
// 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 kPersistenceTimeoutMs = 60 * 1000; // 60s
// The timer used for persisting measurements data.
nsITimer* gPersistenceTimer = nullptr;
+// The worker thread to perform persistence.
+nsCOMPtr<nsIThread> gPersistenceThread = nullptr;
namespace {
+void Persist();
+
/**
- * Get the path to the Android cache storage. This is the suggested
- * location for temporary small files (< 1MB). See
- * https://developer.android.com/guide/topics/data/data-storage.html#filesInternal
- *
- * If it fails to get the cache directory, it will attempt to get
- * an OS temporary dir through |NS_GetSpecialDirectory|.
+ * Get the path to the persistence file in the Android Data dir.
*
* @param {nsCString} aOutDir - the variable holding the path.
* @returns NS_OK if a cache path was found, a failure value otherwise.
*/
nsresult
-GetAndroidCacheDir(nsCString& aOutDir)
+GetPersistenceFilePath(nsCString& aOutDir)
{
// This relies on the Java environment to set the location of the
- // cache directory. If that happens, the following variables are set.
- // However, things can go wrong. In that case, just fall back to
- // |NS_OS_TEMP_DIR|.
- const char *extract = PR_GetEnv("MOZ_LINKER_EXTRACT");
- if (extract && !strncmp(extract, "1", 2 /* Including '\0' */)) {
- const char *cachePath = PR_GetEnv("MOZ_LINKER_CACHE");
- if (cachePath && *cachePath) {
- ANDROID_LOG("GetAndroidCacheDir - Cache dir %s.\n", cachePath);
- aOutDir.Assign(cachePath);
- return NS_OK;
- }
+ // 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("GetPersistenceFilePath - Cannot find the data directory in the environment.\n");
+ return NS_ERROR_FAILURE;
}
- // If we cannot find the cache directory, try something else.
- ANDROID_LOG("GetAndroidCacheDir - Cannot find the cache directory.\n");
-
- nsCOMPtr<nsIFile> osTempDir;
- nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(osTempDir));
- NS_ENSURE_SUCCESS(rv, rv);
-
- aOutDir = osTempDir->HumanReadablePath();
-
- ANDROID_LOG("GetAndroidCacheDir - Falling back to %s.\n", aOutDir.get());
-
+ aOutDir.Assign(dataDir);
+ aOutDir.Append("/gv_measurements.json");
+ ANDROID_LOG("GetPersistenceFilePath - Persistence file path is %s/gv_measurements.json.\n", dataDir);
return NS_OK;
}
/**
* TODO
*/
void
ArmPersistenceTimer()
{
MOZ_ASSERT(NS_IsMainThread());
- gPersistenceTimer =
- NS_NewTimer(mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other)).take();
+ // We won't have a persistence timer the first time this runs, so take
+ // care of that.
if (!gPersistenceTimer) {
- ANDROID_LOG("ArmPersistenceTimer - Timer creation failed.\n");
+ gPersistenceTimer =
+ NS_NewTimer(mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other)).take();
+ if (!gPersistenceTimer) {
+ ANDROID_LOG("ArmPersistenceTimer - Timer creation failed.\n");
+ 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("Persist",
+ []() -> void { ::Persist(); }));
+ };
+
+ // Schedule the timer to automatically run and reschedule
+ // every |kPersistenceTimeoutMs|.
+ gPersistenceTimer->InitWithNamedFuncCallback(timerCallback,
+ nullptr, kPersistenceTimeoutMs,
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+ "TelemetryGeckoViewPersistence::Persist");
+}
+
+void
+Persist()
+{
+ 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("Persist\n");
+
+ // If the function completes or fails, make sure to spin up the persistence timer again.
+ auto scopedArmTimer = mozilla::MakeScopeExit([&] {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ArmPersistenceTimer", []() -> void { ArmPersistenceTimer(); }));
+ });
+
+ // TODO: Remove me, just testing.
+ TelemetryScalar::Add(mozilla::Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 1);
+
+ Json::Value root;
+ Json::Value scalars;
+ if (NS_FAILED(TelemetryScalar::PersistMeasurementsToFile(scalars))) {
+ ANDROID_LOG("Persist - Failed to persist scalars.\n");
+ }
+ root["scalars"] = scalars;
+
+ // Get the file path.
+ nsAutoCString persistenceFilePath;
+ if (NS_FAILED(GetPersistenceFilePath(persistenceFilePath))) {
+ ANDROID_LOG("Persist - Failed to get the path to the persistence file\n");
return;
}
- // Schedule the timer to automatically run and reschedule
- // every |kPersistenceTimeoutMs|.
- gPersistenceTimer->InitWithNamedFuncCallback(TelemetryGeckoViewPersistence::Persist,
- nullptr, kPersistenceTimeoutMs,
- nsITimer::TYPE_REPEATING_SLACK,
- "TelemetryGeckoViewPersistence::Persist");
+ // Write the persistence file to the disk.
+ mozilla::OFStream f(persistenceFilePath.get(), std::ios::out | std::ios::trunc);
+ if (f.is_open()) {
+ Json::FastWriter writer;
+ f << writer.write(root);
+ f.close();
+ ANDROID_LOG("Persist - dump %s\n", writer.write(root).c_str());
+ }
+}
+
+/**
+ * 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
+LoadPersistedData()
+{
+ 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("LoadPersistedData\n");
+
+ // If the function completes or fails, make sure to spin up the persistence timer.
+ auto scopedArmTimer = mozilla::MakeScopeExit([&] {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ArmPersistenceTimer", []() -> void { ArmPersistenceTimer(); }));
+ });
+
+ // Get the file path.
+ nsAutoCString persistenceFilePath;
+ if (NS_FAILED(GetPersistenceFilePath(persistenceFilePath))) {
+ ANDROID_LOG("LoadPersistedData - Failed to get the path to the persistence file\n");
+ return;
+ }
+
+ // TODO: Check if there's a "bak" persistence file: this means Android killed
+ // us while we were persisting.
+
+ // Load the file and parse the JSON.
+ Json::Value root;
+ Json::Reader reader;
+ mozilla::IFStream f(persistenceFilePath.get());
+ if (!reader.parse(f, root, false)) {
+ ANDROID_LOG("LoadPersistedData - Failed to load cache file at %s\n", persistenceFilePath.get());
+ return;
+ }
+
+ if (root.isMember("scalars") && root["scalars"].isObject()) {
+ if (NS_FAILED(TelemetryScalar::LoadPersistedMeasurements(root["scalars"]))) {
+ ANDROID_LOG("LoadPersistedData - Failed to parse 'scalars'.");
+ }
+ }
+
+ // DEBUG TEST:
+ Json::FastWriter writer;
+ ANDROID_LOG("LoadPersistedData - read %s\n", writer.write(root).c_str());
}
} // anonymous namespace
void
TelemetryGeckoViewPersistence::InitPersistence()
{
// We only want to add persistence for GeckoView, but both
@@ -113,103 +202,56 @@ TelemetryGeckoViewPersistence::InitPersi
// order to persist data for all the processes.
if (!XRE_IsParentProcess()) {
ANDROID_LOG("InitPersistence - Bailing out on child process.\n");
return;
}
ANDROID_LOG("InitPersistence\n");
- // Test:
- nsAutoCString testPath;
- if (NS_SUCCEEDED(GetAndroidCacheDir(testPath))) {
- ANDROID_LOG("Cache found!");
+ // Spawn a new thread for handling GeckoView Telemetry persistence I/O.
+ // We just spawn it once and re-use it later.
+ nsresult rv = NS_NewNamedThread("PersistenceIO", getter_AddRefs(gPersistenceThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ANDROID_LOG("InitPersistence - Failed to instantiate the worker thread.\n");
+ return;
}
- TelemetryGeckoViewPersistence::Load();
-
- ArmPersistenceTimer();
+ // Trigger the loading of the persistence data. After the function
+ // completes it will automatically arm the persistence timer.
+ gPersistenceThread->Dispatch(
+ NS_NewRunnableFunction("LoadPersistedData", []() -> void { LoadPersistedData(); }));
}
void
TelemetryGeckoViewPersistence::DeInitPersistence()
{
- ANDROID_LOG("DeInitPersistence\n");
- // TODO: do we need to check if isFennec()?
+ // Bail out if this is Fennec or not the parent process.
+ if (mozilla::jni::IsFennec() || !XRE_IsParentProcess()) {
+ ANDROID_LOG("DeInitPersistence - Bailing out.\n");
+ 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\n");
+
+ if (gPersistenceThread) {
+ gPersistenceThread->Shutdown();
+ gPersistenceThread = nullptr;
+ }
+
if (gPersistenceTimer) {
// Always make sure the timer is canceled.
MOZ_ALWAYS_SUCCEEDS(gPersistenceTimer->Cancel());
- }
- gPersistenceTimer = nullptr;
-}
-
-void
-TelemetryGeckoViewPersistence::Persist(nsITimer* aTimer, void* aClosure)
-{
- MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process.");
- ANDROID_LOG("Persist\n");
-
- // TODO: Remove me, just testing.
- TelemetryScalar::Add(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 1);
-
- Json::Value root;
- Json::Value scalars;
- if (NS_FAILED(TelemetryScalar::PersistMeasurementsToFile(scalars))) {
- ANDROID_LOG("Persist - Failed to persist scalars.\n");
- }
- root["scalars"] = scalars;
-
- // Test: get the file path.
- nsAutoCString testPath;
- if (NS_SUCCEEDED(GetAndroidCacheDir(testPath))) {
- ANDROID_LOG("Cache found!");
- }
- testPath.Append("/gv_measurements.json");
- ANDROID_LOG("Persist - writing to %s\n", testPath.get());
-
- // TODO: this has to happen off the main thread
- mozilla::OFStream f(testPath.get(), std::ios::out | std::ios::trunc);
- if (f.is_open()) {
- Json::FastWriter writer;
- f << writer.write(root);
- f.close();
- ANDROID_LOG("Persist - dump %s\n", writer.write(root).c_str());
+ gPersistenceTimer = nullptr;
}
}
void
-TelemetryGeckoViewPersistence::Load()
+TelemetryGeckoViewPersistence::ClearPersistenceData()
{
- MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process.");
- ANDROID_LOG("Load\n");
-
- // Test: get the file path.
- nsAutoCString testPath;
- if (NS_SUCCEEDED(GetAndroidCacheDir(testPath))) {
- ANDROID_LOG("Cache found!");
- }
- testPath.Append("/gv_measurements.json");
- ANDROID_LOG("Load - loading from %s\n", testPath.get());
-
- // Load the file and parse the JSON.
- // TODO: this has to happen off the main thread.
- Json::Value root;
- Json::Reader reader;
- mozilla::IFStream f(testPath.get());
- if (!reader.parse(f, root, false)) {
- ANDROID_LOG("Load - Failed to load cache file at %s\n", testPath.get());
- return;
- }
-
- if (root.isMember("scalars") && root["scalars"].isObject()) {
- if (NS_FAILED(TelemetryScalar::LoadPersistedMeasurements(root["scalars"]))) {
- ANDROID_LOG("Load - Failed to parse 'scalars'.");
- }
- }
-
- // DEBUG TEST:
- Json::FastWriter writer;
- ANDROID_LOG("Load - read %s\n", writer.write(root).c_str());
+ // TODO:
+ // - Cancel the timer
+ // - delete the persistence file off the main thread.
+ // - re-arm the timer
}