--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -1,14 +1,15 @@
/* -*- 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 "json/json.h"
#include "nsITelemetry.h"
#include "nsIVariant.h"
#include "nsVariant.h"
#include "nsHashKeys.h"
#include "nsBaseHashtable.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsIXPConnect.h"
@@ -838,16 +839,20 @@ KeyedScalar::SizeOfIncludingThis(mozilla
typedef nsUint32HashKey ScalarIDHashKey;
typedef nsUint32HashKey ProcessIDHashKey;
typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
+typedef mozilla::Tuple<const char*, nsCOMPtr<nsIVariant>, uint32_t> ScalarDataTuple;
+typedef nsTArray<ScalarDataTuple> ScalarTupleArray;
+typedef nsDataHashtable<ProcessIDHashKey, ScalarTupleArray> ScalarSnapshotTable;
+
} // namespace
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// PRIVATE STATE, SHARED BY ALL THREADS
namespace {
@@ -1177,21 +1182,25 @@ internal_GetScalarByEnum(const StaticMut
/**
* Update the scalar with the provided value. This is used by the JS API.
*
* @param lock Instance of a lock locking gTelemetryHistogramMutex
* @param aName The scalar name.
* @param aType The action type for updating the scalar.
* @param aValue The value to use for updating the scalar.
+ * @param aProcessOverride The process for which the scalar must be updated.
+ * This must only be used for GeckoView persistence. It must be
+ * set to the ProcessID::Parent for all the other cases.
* @return a ScalarResult error value.
*/
ScalarResult
internal_UpdateScalar(const StaticMutexAutoLock& lock, const nsACString& aName,
- ScalarActionType aType, nsIVariant* aValue)
+ ScalarActionType aType, nsIVariant* aValue,
+ ProcessID aProcessOverride = ProcessID::Parent)
{
ScalarKey uniqueId;
nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId);
if (NS_FAILED(rv)) {
return (rv == NS_ERROR_FAILURE) ?
ScalarResult::NotInitialized : ScalarResult::UnknownScalar;
}
@@ -1215,17 +1224,17 @@ internal_UpdateScalar(const StaticMutexA
}
TelemetryIPCAccumulator::RecordChildScalarAction(
uniqueId.id, uniqueId.dynamic, aType, variantValue.ref());
return ScalarResult::Ok;
}
// Finally get the scalar.
ScalarBase* scalar = nullptr;
- rv = internal_GetScalarByEnum(lock, uniqueId, ProcessID::Parent, &scalar);
+ rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar);
if (NS_FAILED(rv)) {
// Don't throw on expired scalars.
if (rv == NS_ERROR_NOT_AVAILABLE) {
return ScalarResult::Ok;
}
return ScalarResult::UnknownScalar;
}
@@ -1467,16 +1476,85 @@ internal_RegisterScalars(const StaticMut
gDynamicScalarInfo->AppendElement(scalarInfo);
uint32_t scalarId = gDynamicScalarInfo->Length() - 1;
CharPtrEntryType *entry = gScalarNameIDMap.PutEntry(scalarInfo.name());
entry->mData = ScalarKey{scalarId, true};
}
}
+/**
+ * TODO.
+ */
+nsresult
+internal_GetScalarSnapshot(const StaticMutexAutoLock& lock,
+ ScalarSnapshotTable& aScalarsToReflect,
+ unsigned int aDataset, bool aClearScalars)
+{
+ // The snapshotting function is the same for both static and dynamic builtin scalars.
+ // We can use the same function and store the scalars in the same output storage.
+ auto snapshotter = [aDataset, &lock, &aScalarsToReflect]
+ (ProcessesScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic)
+ -> nsresult
+ {
+ // Iterate the scalars in aProcessStorage. The storage may contain empty or yet to be
+ // initialized scalars from all the supported processes.
+ for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
+ ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
+ ScalarTupleArray& processScalars = aScalarsToReflect.GetOrInsert(iter.Key());
+
+ // Are we in the "Dynamic" process?
+ bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
+
+ // Iterate each available child storage.
+ for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+ ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
+
+ // Get the informations for this scalar.
+ const BaseScalarInfo& info =
+ internal_GetScalarInfo(lock, ScalarKey{childIter.Key(),
+ aIsBuiltinDynamic ? true : isDynamicProcess});
+
+ // Serialize the scalar if it's in the desired dataset.
+ if (IsInDataset(info.dataset, aDataset)) {
+ // Get the scalar value.
+ nsCOMPtr<nsIVariant> scalarValue;
+ nsresult rv = scalar->GetValue(scalarValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // Append it to our list.
+ processScalars.AppendElement(mozilla::MakeTuple(info.name(), scalarValue, info.kind));
+ }
+ }
+ }
+ return NS_OK;
+ };
+
+ // Take a snapshot of the scalars.
+ nsresult rv = snapshotter(gScalarStorageMap, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // And a snapshot of the dynamic builtin ones.
+ rv = snapshotter(gDynamicBuiltinScalarStorageMap, true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aClearScalars) {
+ // The map already takes care of freeing the allocated memory.
+ gScalarStorageMap.Clear();
+ gDynamicBuiltinScalarStorageMap.Clear();
+ }
+
+ return NS_OK;
+}
+
} // namespace
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars::
// This is a StaticMutex rather than a plain Mutex (1) so that
@@ -2171,107 +2249,53 @@ TelemetryScalar::CreateSnapshots(unsigne
aResult.setObject(*root_obj);
// Return `{}` in child processes.
if (!XRE_IsParentProcess()) {
return NS_OK;
}
// Only lock the mutex while accessing our data, without locking any JS related code.
- typedef mozilla::Pair<const char*, nsCOMPtr<nsIVariant>> DataPair;
- typedef nsTArray<DataPair> ScalarArray;
- nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
+ ScalarSnapshotTable scalarsToReflect;
{
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
- // The snapshotting function is the same for both static and dynamic builtin scalars.
- // We can use the same function and store the scalars in the same output storage.
- auto snapshotter = [aDataset, &locker, &scalarsToReflect]
- (ProcessesScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic)
- -> nsresult
- {
- // Iterate the scalars in aProcessStorage. The storage may contain empty or yet to be
- // initialized scalars from all the supported processes.
- for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
- ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
- ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
-
- // Are we in the "Dynamic" process?
- bool isDynamicProcess = ProcessID::Dynamic == static_cast<ProcessID>(iter.Key());
-
- // Iterate each available child storage.
- for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
- ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
-
- // Get the informations for this scalar.
- const BaseScalarInfo& info =
- internal_GetScalarInfo(locker, ScalarKey{childIter.Key(),
- aIsBuiltinDynamic ? true : isDynamicProcess});
-
- // Serialize the scalar if it's in the desired dataset.
- if (IsInDataset(info.dataset, aDataset)) {
- // Get the scalar value.
- nsCOMPtr<nsIVariant> scalarValue;
- nsresult rv = scalar->GetValue(scalarValue);
- if (NS_FAILED(rv)) {
- return rv;
- }
- // Append it to our list.
- processScalars.AppendElement(mozilla::MakePair(info.name(), scalarValue));
- }
- }
- }
- return NS_OK;
- };
-
- // Take a snapshot of the scalars.
- nsresult rv = snapshotter(gScalarStorageMap, false);
+ nsresult rv =
+ internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset, aClearScalars);
if (NS_FAILED(rv)) {
return rv;
}
-
- // And a snapshot of the dynamic builtin ones.
- rv = snapshotter(gDynamicBuiltinScalarStorageMap, true);
- if (NS_FAILED(rv)) {
- return rv;
- }
-
- if (aClearScalars) {
- // The map already takes care of freeing the allocated memory.
- gScalarStorageMap.Clear();
- gDynamicBuiltinScalarStorageMap.Clear();
- }
}
// Reflect it to JS.
for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
- ScalarArray& processScalars = iter.Data();
+ ScalarTupleArray& processScalars = iter.Data();
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
// Create the object that will hold the scalars for this process and add it
// to the returned root object.
JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
if (!processObj ||
!JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
- for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length(); i++) {
- const DataPair& scalar = processScalars[i];
+ for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
+ const ScalarDataTuple& scalar = processScalars[i];
// Convert it to a JS Val.
JS::Rooted<JS::Value> scalarJsValue(aCx);
nsresult rv =
- nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, scalar.second(), &scalarJsValue);
+ nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, mozilla::Get<1>(scalar), &scalarJsValue);
if (NS_FAILED(rv)) {
return rv;
}
// Add it to the scalar object.
- if (!JS_DefineProperty(aCx, processObj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) {
+ if (!JS_DefineProperty(aCx, processObj, mozilla::Get<0>(scalar), scalarJsValue, JSPROP_ENUMERATE)) {
return NS_ERROR_FAILURE;
}
}
}
return NS_OK;
}
@@ -2916,8 +2940,146 @@ TelemetryScalar::AddDynamicScalarDefinit
false /* builtin */});
}
{
StaticMutexAutoLock locker(gTelemetryScalarsMutex);
internal_RegisterScalars(locker, dynamicStubs);
}
}
+
+#if defined(MOZ_WIDGET_ANDROID)
+/**
+ * TODO
+ */
+nsresult
+TelemetryScalar::PersistMeasurementsToFile(Json::Value &aWriter)
+{
+ // Get a copy of the data, without clearing.
+ ScalarSnapshotTable scalarsToReflect;
+ {
+ StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+ // For persistence, we care about all the datasets. Worst case, they
+ // will be empty.
+ nsresult rv = internal_GetScalarSnapshot(locker,
+ scalarsToReflect,
+ nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
+ false /*aClearScalars*/);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // Serialize the measurements to a file.
+ for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
+ ScalarTupleArray& processScalars = iter.Data();
+ const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
+
+ Json::Value currentProcess;
+
+ for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) {
+ const ScalarDataTuple& scalar = processScalars[i];
+
+ switch (mozilla::Get<2>(scalar)) {
+ case nsITelemetry::SCALAR_TYPE_COUNT:
+ {
+ uint32_t val = 0;
+ nsresult rv = mozilla::Get<1>(scalar)->GetAsUint32(&val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentProcess[mozilla::Get<0>(scalar)] = val;
+ break;
+ }
+ case nsITelemetry::SCALAR_TYPE_STRING:
+ {
+ nsString val;
+ nsresult rv = mozilla::Get<1>(scalar)->GetAsAString(val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentProcess[mozilla::Get<0>(scalar)] = PromiseFlatString(val).get();
+ break;
+ }
+ case nsITelemetry::SCALAR_TYPE_BOOLEAN:
+ {
+ bool val = false;
+ nsresult rv = mozilla::Get<1>(scalar)->GetAsBool(&val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentProcess[mozilla::Get<0>(scalar)] = val;
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Unknown scalar kind.");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ aWriter[processName] = currentProcess;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * TODO
+ */
+nsresult
+TelemetryScalar::LoadPersistedMeasurements(const Json::Value& aData)
+{
+ MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process");
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // TODO: this must become an utility or something.
+ nsDataHashtable<nsCStringHashKey, ProcessID> processNameMap;
+ for (uint32_t id = 0; id < static_cast<uint32_t>(ProcessID::Count); id++) {
+ processNameMap.Put(nsAutoCString(GetNameForProcessID(ProcessID(id))),
+ ProcessID(id));
+ }
+
+ // TODO: check scalar.asString() - can it ever return null?
+
+ StaticMutexAutoLock lock(gTelemetryScalarsMutex);
+
+ // Iterate through all the scalars.
+ for (const auto& processName : aData.getMemberNames()) {
+ nsAutoCString internalProcName(processName.c_str());
+ if (!processNameMap.Contains(internalProcName)) {
+ NS_WARNING(nsPrintfCString("Failed to get process ID for %s", processName.c_str()));
+ continue;
+ }
+
+ const Json::Value& processData = aData[processName];
+ for (const auto& scalarName : processData.getMemberNames()) {
+ const Json::Value& scalar = processData[scalarName];
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIWritableVariant> persistedVal(new nsVariant());
+
+ switch (scalar.type()) {
+ case Json::ValueType::intValue:
+ case Json::ValueType::uintValue:
+ case Json::ValueType::realValue:
+ rv = persistedVal->SetAsUint32(scalar.asUInt());
+ break;
+ case Json::ValueType::stringValue:
+ rv = persistedVal->SetAsAString(NS_ConvertUTF8toUTF16(scalar.asString().c_str()));
+ break;
+ case Json::ValueType::booleanValue:
+ rv = persistedVal->SetAsBool(scalar.asBool());
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown scalar kind.");
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING(nsPrintfCString("Failed to set the value for %s", scalarName.c_str()));
+ continue;
+ }
+
+ mozilla::Unused << internal_UpdateScalar(lock,
+ nsAutoCString(scalarName.c_str()),
+ ScalarActionType::eSet,
+ persistedVal,
+ processNameMap.Get(internalProcName));
+ }
+ }
+
+ return NS_OK;
+}
+#endif // MOZ_WIDGET_ANDROID
--- a/toolkit/components/telemetry/geckoview/GeckoViewTelemetryPersistence.cpp
+++ b/toolkit/components/telemetry/geckoview/GeckoViewTelemetryPersistence.cpp
@@ -2,21 +2,25 @@
/* 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 "GeckoViewTelemetryPersistence.h"
#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/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
@@ -100,24 +104,33 @@ TelemetryGeckoViewPersistence::InitPersi
{
// We only want to add persistence for GeckoView, but both
// GV and Fennec are on Android. Bail out if this is Fennec.
if (mozilla::jni::IsFennec()) {
ANDROID_LOG("InitPersistence - Bailing out on Fennec.\n");
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.\n");
+ return;
+ }
+
ANDROID_LOG("InitPersistence\n");
// Test:
nsAutoCString testPath;
if (NS_SUCCEEDED(GetAndroidCacheDir(testPath))) {
ANDROID_LOG("Cache found!");
}
+ TelemetryGeckoViewPersistence::Load();
+
ArmPersistenceTimer();
}
void
TelemetryGeckoViewPersistence::DeInitPersistence()
{
ANDROID_LOG("DeInitPersistence\n");
// TODO: do we need to check if isFennec()?
@@ -130,10 +143,73 @@ TelemetryGeckoViewPersistence::DeInitPer
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());
+ }
}
+
+void
+TelemetryGeckoViewPersistence::Load()
+{
+ 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());
+}