/src/mozilla-central/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "TelemetryGeckoViewPersistence.h" |
8 | | |
9 | | #include "jsapi.h" |
10 | | #include "js/JSON.h" |
11 | | #include "mozilla/ErrorNames.h" |
12 | | #include "mozilla/JSONWriter.h" |
13 | | #include "mozilla/Path.h" |
14 | | #include "mozilla/Preferences.h" |
15 | | #include "mozilla/ScopeExit.h" |
16 | | #include "mozilla/StaticPtr.h" |
17 | | #include "mozilla/SystemGroup.h" |
18 | | #include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI |
19 | | #include "mozilla/dom/SimpleGlobalObject.h" |
20 | | #include "nsDirectoryServiceDefs.h" |
21 | | #include "nsIFile.h" |
22 | | #include "nsIInputStream.h" |
23 | | #include "nsIObserverService.h" |
24 | | #include "nsIOutputStream.h" |
25 | | #include "nsISafeOutputStream.h" |
26 | | #include "nsITimer.h" |
27 | | #include "nsLocalFile.h" |
28 | | #include "nsNetUtil.h" |
29 | | #include "nsXULAppAPI.h" |
30 | | #include "prenv.h" |
31 | | #include "prio.h" |
32 | | #include "core/TelemetryScalar.h" |
33 | | #include "core/TelemetryHistogram.h" |
34 | | #include "xpcpublic.h" |
35 | | |
36 | | using mozilla::GetErrorName; |
37 | | using mozilla::MakeScopeExit; |
38 | | using mozilla::Preferences; |
39 | | using mozilla::StaticRefPtr; |
40 | | using mozilla::SystemGroup; |
41 | | using mozilla::TaskCategory; |
42 | | using mozilla::dom::AutoJSAPI; |
43 | | using mozilla::dom::SimpleGlobalObject; |
44 | | |
45 | | using PathChar = mozilla::filesystem::Path::value_type; |
46 | | using PathCharPtr = const PathChar*; |
47 | | |
48 | | // Enable logging by default on Debug builds. |
49 | | #ifdef DEBUG |
50 | | // If we're building for Android, use the provided logging facility. |
51 | | #ifdef MOZ_WIDGET_ANDROID |
52 | | #include <android/log.h> |
53 | | #define ANDROID_LOG(fmt, ...) \ |
54 | | __android_log_print(ANDROID_LOG_DEBUG, "Telemetry", fmt, ##__VA_ARGS__) |
55 | | #else |
56 | | // If we're building for other platforms (e.g. for running test coverage), try |
57 | | // to print something anyway. |
58 | | #define ANDROID_LOG(...) printf_stderr("\n**** TELEMETRY: " __VA_ARGS__) |
59 | | #endif // MOZ_WIDGET_ANDROID |
60 | | #else |
61 | | // No-op on Release builds. |
62 | | #define ANDROID_LOG(...) |
63 | | #endif // DEBUG |
64 | | |
65 | | // The Gecko runtime can be killed at anytime. Moreover, we can |
66 | | // have very short lived sessions. The persistence timeout governs |
67 | | // how frequently measurements are saved to disk. |
68 | | const uint32_t kDefaultPersistenceTimeoutMs = 60 * 1000; // 60s |
69 | | |
70 | | // The name of the persistence file used for saving the |
71 | | // measurements. |
72 | | const char16_t kPersistenceFileName[] = u"gv_measurements.json"; |
73 | | |
74 | | // This topic is notified and propagated up to the application to |
75 | | // make sure it knows that data loading has complete and that snapshotting |
76 | | // can now be performed. |
77 | | const char kLoadCompleteTopic[] = "internal-telemetry-geckoview-load-complete"; |
78 | | |
79 | | // The timer used for persisting measurements data. |
80 | | nsITimer* gPersistenceTimer; |
81 | | // The worker thread to perform persistence. |
82 | | StaticRefPtr<nsIThread> gPersistenceThread; |
83 | | |
84 | | namespace { |
85 | | |
86 | | void PersistenceThreadPersist(); |
87 | | |
88 | | /** |
89 | | + * The helper class used by mozilla::JSONWriter to |
90 | | + * serialize the JSON structure to a file. |
91 | | + */ |
92 | | class StreamingJSONWriter : public mozilla::JSONWriteFunc |
93 | | { |
94 | | public: |
95 | | nsresult Open(nsCOMPtr<nsIFile> aOutFile) |
96 | 0 | { |
97 | 0 | MOZ_ASSERT(!mStream, "Open must not be called twice"); |
98 | 0 | nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(mStream), aOutFile); |
99 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
100 | 0 | return NS_OK; |
101 | 0 | } |
102 | | |
103 | | nsresult Close() |
104 | 0 | { |
105 | 0 | MOZ_ASSERT(mStream, "Close must be called on an already opened stream"); |
106 | 0 | // We don't need to care too much about checking if count matches |
107 | 0 | // the length of aData: Finish() will do that for us and fail if |
108 | 0 | // Write did not persist all the data or mStream->Close() failed. |
109 | 0 | // Note that |nsISafeOutputStream| will write to a temp file and only |
110 | 0 | // overwrite the destination if no error was reported. |
111 | 0 | nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(mStream); |
112 | 0 | MOZ_ASSERT(safeStream); |
113 | 0 | return safeStream->Finish(); |
114 | 0 | } |
115 | | |
116 | | void Write(const char* aStr) override |
117 | 0 | { |
118 | 0 | uint32_t count; |
119 | 0 | mozilla::Unused << mStream->Write(aStr, strlen(aStr), &count); |
120 | 0 | } |
121 | | |
122 | | private: |
123 | | nsCOMPtr<nsIOutputStream> mStream; |
124 | | }; |
125 | | |
126 | | /** |
127 | | * Get the path to the Android Data dir. |
128 | | * |
129 | | * @param {nsTString<PathChar>} aOutDir - the variable holding the path. |
130 | | * @return {nsresult} NS_OK if the data dir path was found, a failure value otherwise. |
131 | | */ |
132 | | nsresult |
133 | | GetAndroidDataDir(nsTString<PathChar>& aOutDir) |
134 | 0 | { |
135 | 0 | // This relies on the Java environment to set the location of the |
136 | 0 | // cache directory. If that happens, the following variable is set. |
137 | 0 | // This should always be the case. |
138 | 0 | const char *dataDir = PR_GetEnv("MOZ_ANDROID_DATA_DIR"); |
139 | 0 | if (!dataDir || !*dataDir) { |
140 | 0 | ANDROID_LOG("GetAndroidDataDir - Cannot find the data directory in the environment."); |
141 | 0 | return NS_ERROR_FAILURE; |
142 | 0 | } |
143 | 0 | |
144 | 0 | aOutDir.AssignASCII(dataDir); |
145 | 0 | return NS_OK; |
146 | 0 | } |
147 | | |
148 | | /** |
149 | | * Get the path to the persistence file in the Android Data dir. |
150 | | * |
151 | | * @param {nsCOMPtr<nsIFile>} aOutFile - the nsIFile pointer holding the file info. |
152 | | * @return {nsresult} NS_OK if the persistence file was found, a failure value otherwise. |
153 | | */ |
154 | | nsresult |
155 | | GetPersistenceFile(nsCOMPtr<nsIFile>& aOutFile) |
156 | 0 | { |
157 | 0 | nsTString<PathChar> dataDir; |
158 | 0 | nsresult rv = GetAndroidDataDir(dataDir); |
159 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
160 | 0 |
|
161 | 0 | // Append the extension to the filename. |
162 | 0 | nsAutoString fileName; |
163 | 0 | fileName.Assign(kPersistenceFileName); |
164 | 0 |
|
165 | 0 | aOutFile = new nsLocalFile(dataDir); |
166 | 0 | aOutFile->Append(fileName); |
167 | 0 | ANDROID_LOG("GetPersistenceFile - %s", aOutFile->HumanReadablePath().get()); |
168 | 0 | return NS_OK; |
169 | 0 | } |
170 | | |
171 | | /** |
172 | | * Read and parses JSON content from a file. |
173 | | * |
174 | | * @param {nsCOMPtr<nsIFile>} aFile - the nsIFile handle to the file. |
175 | | * @param {nsACString} fileContent - the content of the file. |
176 | | * @return {nsresult} NS_OK if the file was correctly read, an error code otherwise. |
177 | | */ |
178 | | nsresult |
179 | | ReadFromFile(const nsCOMPtr<nsIFile>& aFile, nsACString& fileContent) |
180 | 0 | { |
181 | 0 | int64_t fileSize = 0; |
182 | 0 | nsresult rv = aFile->GetFileSize(&fileSize); |
183 | 0 | if (NS_FAILED(rv)) { |
184 | 0 | return rv; |
185 | 0 | } |
186 | 0 | |
187 | 0 | nsCOMPtr<nsIInputStream> inStream; |
188 | 0 | rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), |
189 | 0 | aFile, |
190 | 0 | PR_RDONLY); |
191 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
192 | 0 |
|
193 | 0 | // Make sure to close the stream. |
194 | 0 | auto scopedStreamClose = MakeScopeExit([inStream] { inStream->Close(); }); |
195 | 0 |
|
196 | 0 | rv = NS_ReadInputStreamToString(inStream, fileContent, fileSize); |
197 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
198 | 0 |
|
199 | 0 | return NS_OK; |
200 | 0 | } |
201 | | |
202 | | /** |
203 | | * Arms the persistence timer and instructs to run the persistence |
204 | | * task off the main thread. |
205 | | */ |
206 | | void |
207 | | MainThreadArmPersistenceTimer() |
208 | 0 | { |
209 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
210 | 0 | ANDROID_LOG("MainThreadArmPersistenceTimer"); |
211 | 0 |
|
212 | 0 | // We won't have a persistence timer the first time this runs, so take |
213 | 0 | // care of that. |
214 | 0 | if (!gPersistenceTimer) { |
215 | 0 | gPersistenceTimer = |
216 | 0 | NS_NewTimer(SystemGroup::EventTargetFor(TaskCategory::Other)).take(); |
217 | 0 | if (!gPersistenceTimer) { |
218 | 0 | ANDROID_LOG("MainThreadArmPersistenceTimer - Timer creation failed."); |
219 | 0 | return; |
220 | 0 | } |
221 | 0 | } |
222 | 0 | |
223 | 0 | // Define the callback for the persistence timer: it will dispatch the persistence |
224 | 0 | // task off the main thread. Once finished, it will trigger the timer again. |
225 | 0 | nsTimerCallbackFunc timerCallback = [](nsITimer* aTimer, void* aClosure) { |
226 | 0 | gPersistenceThread->Dispatch(NS_NewRunnableFunction("PersistenceThreadPersist", |
227 | 0 | []() -> void { ::PersistenceThreadPersist(); })); |
228 | 0 | }; |
229 | 0 |
|
230 | 0 | uint32_t timeout = Preferences::GetUint("toolkit.telemetry.geckoPersistenceTimeout", |
231 | 0 | kDefaultPersistenceTimeoutMs); |
232 | 0 |
|
233 | 0 | // Schedule the timer to automatically run and reschedule |
234 | 0 | // every |kPersistenceTimeoutMs|. |
235 | 0 | gPersistenceTimer->InitWithNamedFuncCallback(timerCallback, |
236 | 0 | nullptr, |
237 | 0 | timeout, |
238 | 0 | nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, |
239 | 0 | "TelemetryGeckoViewPersistence::Persist"); |
240 | 0 | } |
241 | | |
242 | | /** |
243 | | * Parse the string data into a JSON structure, using |
244 | | * the native JS JSON parser. |
245 | | */ |
246 | | void |
247 | | MainThreadParsePersistedProbes(const nsACString& aProbeData) |
248 | 0 | { |
249 | 0 | // We're required to run on the main thread since we're using JS. |
250 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
251 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes"); |
252 | 0 |
|
253 | 0 | // We need a JS context to run the parsing stuff in. |
254 | 0 | JSObject* cleanGlobal = |
255 | 0 | SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail); |
256 | 0 | if (!cleanGlobal) { |
257 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to create a JS global object"); |
258 | 0 | return; |
259 | 0 | } |
260 | 0 | |
261 | 0 | AutoJSAPI jsapi; |
262 | 0 | if (NS_WARN_IF(!jsapi.Init(cleanGlobal))) { |
263 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to get JS API"); |
264 | 0 | return; |
265 | 0 | } |
266 | 0 | |
267 | 0 | // Parse the JSON using the JS API. |
268 | 0 | JS::RootedValue data(jsapi.cx()); |
269 | 0 | NS_ConvertUTF8toUTF16 utf16Content(aProbeData); |
270 | 0 | if (!JS_ParseJSON(jsapi.cx(), utf16Content.BeginReading(), utf16Content.Length(), &data)) { |
271 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse the persisted JSON"); |
272 | 0 | return; |
273 | 0 | } |
274 | 0 | |
275 | 0 | // Get the data for the scalars. |
276 | 0 | JS::RootedObject dataObj(jsapi.cx(), &data.toObject()); |
277 | 0 | JS::RootedValue scalarData(jsapi.cx()); |
278 | 0 | if (JS_GetProperty(jsapi.cx(), dataObj, "scalars", &scalarData)) { |
279 | 0 | // If the data is an object, try to parse its properties. If not, |
280 | 0 | // silently skip and try to load the other sections. |
281 | 0 | if (!scalarData.isObject() |
282 | 0 | || NS_FAILED(TelemetryScalar::DeserializePersistedScalars(jsapi.cx(), scalarData))) { |
283 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse 'scalars'."); |
284 | 0 | MOZ_ASSERT(!JS_IsExceptionPending(jsapi.cx()), "Parsers must suppress exceptions themselves"); |
285 | 0 | } |
286 | 0 | } else { |
287 | 0 | // Getting the "scalars" property failed, suppress the exception |
288 | 0 | // and continue. |
289 | 0 | JS_ClearPendingException(jsapi.cx()); |
290 | 0 | } |
291 | 0 |
|
292 | 0 | JS::RootedValue keyedScalarData(jsapi.cx()); |
293 | 0 | if (JS_GetProperty(jsapi.cx(), dataObj, "keyedScalars", &keyedScalarData)) { |
294 | 0 | // If the data is an object, try to parse its properties. If not, |
295 | 0 | // silently skip and try to load the other sections. |
296 | 0 | if (!keyedScalarData.isObject() |
297 | 0 | || NS_FAILED(TelemetryScalar::DeserializePersistedKeyedScalars(jsapi.cx(), keyedScalarData))) { |
298 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse 'keyedScalars'."); |
299 | 0 | MOZ_ASSERT(!JS_IsExceptionPending(jsapi.cx()), "Parsers must suppress exceptions themselves"); |
300 | 0 | } |
301 | 0 | } else { |
302 | 0 | // Getting the "keyedScalars" property failed, suppress the exception |
303 | 0 | // and continue. |
304 | 0 | JS_ClearPendingException(jsapi.cx()); |
305 | 0 | } |
306 | 0 |
|
307 | 0 | // Get the data for the histograms. |
308 | 0 | JS::RootedValue histogramData(jsapi.cx()); |
309 | 0 | if (JS_GetProperty(jsapi.cx(), dataObj, "histograms", &histogramData)) { |
310 | 0 | // If the data is an object, try to parse its properties. If not, |
311 | 0 | // silently skip and try to load the other sections. |
312 | 0 | nsresult rv = NS_OK; |
313 | 0 | if (!histogramData.isObject() |
314 | 0 | || NS_FAILED(rv = TelemetryHistogram::DeserializeHistograms(jsapi.cx(), histogramData))) { |
315 | 0 | nsAutoCString errorName; |
316 | 0 | GetErrorName(rv, errorName); |
317 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse 'histograms', %s.", |
318 | 0 | errorName.get()); |
319 | 0 | MOZ_ASSERT(!JS_IsExceptionPending(jsapi.cx()), "Parsers must suppress exceptions themselves"); |
320 | 0 | } |
321 | 0 | } else { |
322 | 0 | // Getting the "histogramData" property failed, suppress the exception |
323 | 0 | // and continue. |
324 | 0 | JS_ClearPendingException(jsapi.cx()); |
325 | 0 | } |
326 | 0 |
|
327 | 0 | // Get the data for the keyed histograms. |
328 | 0 | JS::RootedValue keyedHistogramData(jsapi.cx()); |
329 | 0 | if (JS_GetProperty(jsapi.cx(), dataObj, "keyedHistograms", &keyedHistogramData)) { |
330 | 0 | // If the data is an object, try to parse its properties. If not, |
331 | 0 | // silently skip and try to load the other sections. |
332 | 0 | nsresult rv = NS_OK; |
333 | 0 | if (!keyedHistogramData.isObject() |
334 | 0 | || NS_FAILED(rv = TelemetryHistogram::DeserializeKeyedHistograms(jsapi.cx(), |
335 | 0 | keyedHistogramData))) { |
336 | 0 | nsAutoCString errorName; |
337 | 0 | GetErrorName(rv, errorName); |
338 | 0 | ANDROID_LOG("MainThreadParsePersistedProbes - Failed to parse 'keyedHistograms', %s.", |
339 | 0 | errorName.get()); |
340 | 0 | MOZ_ASSERT(!JS_IsExceptionPending(jsapi.cx()), "Parsers must suppress exceptions themselves"); |
341 | 0 | } |
342 | 0 | } else { |
343 | 0 | // Getting the "keyedHistogramData" property failed, suppress the exception |
344 | 0 | // and continue. |
345 | 0 | JS_ClearPendingException(jsapi.cx()); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | | /** |
350 | | * The persistence worker function, meant to be run off the main thread. |
351 | | */ |
352 | | void |
353 | | PersistenceThreadPersist() |
354 | 0 | { |
355 | 0 | MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process."); |
356 | 0 | MOZ_ASSERT(!NS_IsMainThread(), "This function must be called off the main thread."); |
357 | 0 | ANDROID_LOG("PersistenceThreadPersist"); |
358 | 0 |
|
359 | 0 | // If the function completes or fails, make sure to spin up the persistence timer again. |
360 | 0 | auto scopedArmTimer = MakeScopeExit([&] { |
361 | 0 | NS_DispatchToMainThread( |
362 | 0 | NS_NewRunnableFunction("MainThreadArmPersistenceTimer", []() -> void { |
363 | 0 | MainThreadArmPersistenceTimer(); |
364 | 0 | })); |
365 | 0 | }); |
366 | 0 |
|
367 | 0 | TelemetryScalar::Add(mozilla::Telemetry::ScalarID::TELEMETRY_PERSISTENCE_TIMER_HIT_COUNT, 1); |
368 | 0 |
|
369 | 0 | nsCOMPtr<nsIFile> persistenceFile; |
370 | 0 | if (NS_FAILED(GetPersistenceFile(persistenceFile))) { |
371 | 0 | ANDROID_LOG("PersistenceThreadPersist - Failed to get the persistence file."); |
372 | 0 | return; |
373 | 0 | } |
374 | 0 | |
375 | 0 | // Open the persistence file. |
376 | 0 | mozilla::UniquePtr<StreamingJSONWriter> jsonWriter = |
377 | 0 | mozilla::MakeUnique<StreamingJSONWriter>(); |
378 | 0 |
|
379 | 0 | if (!jsonWriter || NS_FAILED(jsonWriter->Open(persistenceFile))) { |
380 | 0 | ANDROID_LOG("PersistenceThreadPersist - There was an error opening the persistence file."); |
381 | 0 | return; |
382 | 0 | } |
383 | 0 | |
384 | 0 | // Build the JSON structure: give up the ownership of jsonWriter. |
385 | 0 | mozilla::JSONWriter w(std::move(jsonWriter)); |
386 | 0 | w.Start(); |
387 | 0 |
|
388 | 0 | w.StartObjectProperty("scalars"); |
389 | 0 | if (NS_FAILED(TelemetryScalar::SerializeScalars(w))) { |
390 | 0 | ANDROID_LOG("Persist - Failed to persist scalars."); |
391 | 0 | } |
392 | 0 | w.EndObject(); |
393 | 0 |
|
394 | 0 | w.StartObjectProperty("keyedScalars"); |
395 | 0 | if (NS_FAILED(TelemetryScalar::SerializeKeyedScalars(w))) { |
396 | 0 | ANDROID_LOG("Persist - Failed to persist keyed scalars."); |
397 | 0 | } |
398 | 0 | w.EndObject(); |
399 | 0 |
|
400 | 0 | w.StartObjectProperty("histograms"); |
401 | 0 | if (NS_FAILED(TelemetryHistogram::SerializeHistograms(w))) { |
402 | 0 | ANDROID_LOG("Persist - Failed to persist histograms."); |
403 | 0 | } |
404 | 0 | w.EndObject(); |
405 | 0 |
|
406 | 0 | w.StartObjectProperty("keyedHistograms"); |
407 | 0 | if (NS_FAILED(TelemetryHistogram::SerializeKeyedHistograms(w))) { |
408 | 0 | ANDROID_LOG("Persist - Failed to persist keyed histograms."); |
409 | 0 | } |
410 | 0 | w.EndObject(); |
411 | 0 |
|
412 | 0 | // End the building process. |
413 | 0 | w.End(); |
414 | 0 |
|
415 | 0 | // Android can kill us while we are writing to disk and, if that happens, |
416 | 0 | // we end up with a corrupted json overwriting the old session data. |
417 | 0 | // Luckily, |StreamingJSONWriter::Close| is smart enough to write to a |
418 | 0 | // temporary file and only overwrite the original file if nothing bad happened. |
419 | 0 | nsresult rv = static_cast<StreamingJSONWriter*>(w.WriteFunc())->Close(); |
420 | 0 | if (NS_FAILED(rv)) { |
421 | 0 | ANDROID_LOG("PersistenceThreadPersist - There was an error writing to the persistence file."); |
422 | 0 | return; |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | | /** |
427 | | * This function loads the persisted metrics from a JSON file |
428 | | * and adds them to the related storage. After it completes, |
429 | | * it spins up the persistence timer. |
430 | | * |
431 | | * Please note that this function is meant to be run off the |
432 | | * main-thread. |
433 | | */ |
434 | | void |
435 | | PersistenceThreadLoadData() |
436 | 0 | { |
437 | 0 | MOZ_ASSERT(XRE_IsParentProcess(), "We must only persist from the parent process."); |
438 | 0 | MOZ_ASSERT(!NS_IsMainThread(), "We must perform I/O off the main thread."); |
439 | 0 | ANDROID_LOG("PersistenceThreadLoadData"); |
440 | 0 |
|
441 | 0 | // If the function completes or fails, make sure to spin up the persistence timer. |
442 | 0 | nsAutoCString fileContent; |
443 | 0 | auto scopedArmTimer = MakeScopeExit([&] { |
444 | 0 | NS_DispatchToMainThread( |
445 | 0 | NS_NewRunnableFunction("MainThreadArmPersistenceTimer", [fileContent]() -> void { |
446 | 0 | // Try to parse the probes if the file was not empty. |
447 | 0 | if (!fileContent.IsEmpty()) { |
448 | 0 | MainThreadParsePersistedProbes(fileContent); |
449 | 0 | } |
450 | 0 |
|
451 | 0 | TelemetryScalar::ApplyPendingOperations(); |
452 | 0 |
|
453 | 0 | // Arm the timer. |
454 | 0 | MainThreadArmPersistenceTimer(); |
455 | 0 | // Notify that we're good to take snapshots! |
456 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
457 | 0 | if (os) { |
458 | 0 | os->NotifyObservers(nullptr, kLoadCompleteTopic, nullptr); |
459 | 0 | } |
460 | 0 | })); |
461 | 0 | }); |
462 | 0 |
|
463 | 0 | // Attempt to load the persistence file. This could fail if we're not able |
464 | 0 | // to allocate enough memory for the content. See bug 1460911. |
465 | 0 | nsCOMPtr<nsIFile> persistenceFile; |
466 | 0 | if (NS_FAILED(GetPersistenceFile(persistenceFile)) |
467 | 0 | || NS_FAILED(ReadFromFile(persistenceFile, fileContent))) { |
468 | 0 | ANDROID_LOG("PersistenceThreadLoadData - Failed to load cache file at %s", |
469 | 0 | persistenceFile->HumanReadablePath().get()); |
470 | 0 | return; |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | | } // anonymous namespace |
475 | | |
476 | | // This namespace exposes testing only helpers to simplify writing |
477 | | // gtest cases. |
478 | | namespace TelemetryGeckoViewTesting { |
479 | | |
480 | | void |
481 | | TestDispatchPersist() |
482 | 0 | { |
483 | 0 | gPersistenceThread->Dispatch(NS_NewRunnableFunction("Persist", |
484 | 0 | []() -> void { ::PersistenceThreadPersist(); })); |
485 | 0 | } |
486 | | |
487 | | } // GeckoViewTesting |
488 | | |
489 | | void |
490 | | TelemetryGeckoViewPersistence::InitPersistence() |
491 | 0 | { |
492 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
493 | 0 |
|
494 | 0 | if (gPersistenceThread) { |
495 | 0 | ANDROID_LOG("Init must only be called once."); |
496 | 0 | return; |
497 | 0 | } |
498 | 0 | |
499 | 0 | // Only register the persistence timer in the parent process in |
500 | 0 | // order to persist data for all the processes. |
501 | 0 | if (!XRE_IsParentProcess()) { |
502 | 0 | ANDROID_LOG("InitPersistence - Bailing out on child process."); |
503 | 0 | return; |
504 | 0 | } |
505 | 0 | |
506 | 0 | ANDROID_LOG("InitPersistence"); |
507 | 0 |
|
508 | 0 | // Spawn a new thread for handling GeckoView Telemetry persistence I/O. |
509 | 0 | // We just spawn it once and re-use it later. |
510 | 0 | nsCOMPtr<nsIThread> thread; |
511 | 0 | nsresult rv = |
512 | 0 | NS_NewNamedThread("TelemetryGVIO", getter_AddRefs(thread)); |
513 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
514 | 0 | ANDROID_LOG("InitPersistence - Failed to instantiate the worker thread."); |
515 | 0 | return; |
516 | 0 | } |
517 | 0 | |
518 | 0 | gPersistenceThread = thread.forget(); |
519 | 0 |
|
520 | 0 | // From now on all scalar operations should be recorded. |
521 | 0 | TelemetryScalar::DeserializationStarted(); |
522 | 0 |
|
523 | 0 | // Trigger the loading of the persistence data. After the function |
524 | 0 | // completes it will automatically arm the persistence timer. |
525 | 0 | gPersistenceThread->Dispatch( |
526 | 0 | NS_NewRunnableFunction("PersistenceThreadLoadData", &PersistenceThreadLoadData)); |
527 | 0 | } |
528 | | |
529 | | void |
530 | | TelemetryGeckoViewPersistence::DeInitPersistence() |
531 | 0 | { |
532 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
533 | 0 |
|
534 | 0 | // Bail out if this is not the parent process. |
535 | 0 | if (!XRE_IsParentProcess()) { |
536 | 0 | ANDROID_LOG("DeInitPersistence - Bailing out."); |
537 | 0 | return; |
538 | 0 | } |
539 | 0 | |
540 | 0 | // Even though we need to implement this function, it might end up |
541 | 0 | // not being called: Android might kill us without notice to reclaim |
542 | 0 | // our memory in case some other foreground application needs it. |
543 | 0 | ANDROID_LOG("DeInitPersistence"); |
544 | 0 |
|
545 | 0 | if (gPersistenceThread) { |
546 | 0 | gPersistenceThread->Shutdown(); |
547 | 0 | gPersistenceThread = nullptr; |
548 | 0 | } |
549 | 0 |
|
550 | 0 | if (gPersistenceTimer) { |
551 | 0 | // Always make sure the timer is canceled. |
552 | 0 | MOZ_ALWAYS_SUCCEEDS(gPersistenceTimer->Cancel()); |
553 | 0 | NS_RELEASE(gPersistenceTimer); |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | | void |
558 | | TelemetryGeckoViewPersistence::ClearPersistenceData() |
559 | 0 | { |
560 | 0 | // This can be run on any thread, as we just dispatch the persistence |
561 | 0 | // task to the persistence thread. |
562 | 0 | MOZ_ASSERT(gPersistenceThread); |
563 | 0 |
|
564 | 0 | ANDROID_LOG("ClearPersistenceData"); |
565 | 0 |
|
566 | 0 | // Trigger clearing the persisted measurements off the main thread. |
567 | 0 | gPersistenceThread->Dispatch(NS_NewRunnableFunction("ClearPersistedData", |
568 | 0 | []() -> void { |
569 | 0 | nsCOMPtr<nsIFile> persistenceFile; |
570 | 0 | if (NS_FAILED(GetPersistenceFile(persistenceFile)) || |
571 | 0 | NS_FAILED(persistenceFile->Remove(false))) { |
572 | 0 | ANDROID_LOG("ClearPersistenceData - Failed to remove the persistence file."); |
573 | 0 | return; |
574 | 0 | } |
575 | 0 | })); |
576 | 0 | } |