.. _healthreport_architecture:

============
Architecture
============

``healthreporter.sys.mjs`` contains the main interface for FHR, the
``HealthReporter`` type. An instance of this is created by the
``data_reporting_service``.

``providers.sys.mjs`` contains numerous ``Metrics.Provider`` and
``Metrics.Measurement`` used for collecting application metrics. If you
are looking for the FHR probes, this is where they are.

Storage
=======

Firefox Health Report stores data in 3 locations:

* Metrics measurements and provider state is stored in a SQLite database
  (via ``Metrics.Storage``).
* Service state (such as the IDs of documents uploaded) is stored in a
  JSON file on disk (via OS.File).
* Lesser state and run-time options are stored in preferences.

Preferences
===========

Preferences controlling behavior of Firefox Health Report live in the
``datareporting.healthreport.*`` branch.

Service and Data Control
------------------------

The follow preferences control behavior of the service and data upload.

service.enabled
   Controls whether the entire health report service runs. The overall
   service performs data collection, storing, and submission.

   This is the primary kill switch for Firefox Health Report
   outside of the build system variable. i.e. if you are using an
   official Firefox build and wish to disable FHR, this is what you
   should set to false to prevent FHR from not only submitting but
   also collecting data.

uploadEnabled
   Whether uploading of data is enabled. This is the preference the
   checkbox in the preferences UI reflects. If this is
   disabled, FHR still collects data - it just doesn't upload it.

service.loadDelayMsec
   How long (in milliseconds) after initial application start should FHR
   wait before initializing.

   FHR may initialize sooner than this if the FHR service is requested.
   This will happen if e.g. the user goes to ``about:healthreport``.

service.loadDelayFirstRunMsec
   How long (in milliseconds) FHR should wait to initialize on first
   application run.

   FHR waits longer than normal to initialize on first application run
   because first-time initialization can use a lot of I/O to initialize
   the SQLite database and this I/O should not interfere with the
   first-run user experience.

documentServerURI
   The URI of a Bagheera server that FHR should interface with for
   submitting documents.

   You typically do not need to change this.

documentServerNamespace
   The namespace on the document server FHR should upload documents to.

   You typically do not need to change this.

infoURL
   The URL of a page containing more info about FHR, it's privacy
   policy, etc.

about.reportUrl
   The URL to load in ``about:healthreport``.

about.reportUrlUnified
   The URL to load in ``about:healthreport``. This is used instead of ``reportUrl`` for UnifiedTelemetry when it is not opt-in.

service.providerCategories
   A comma-delimited list of category manager categories that contain
   registered ``Metrics.Provider`` records. Read below for how provider
   registration works.

If the entire service is disabled, you lose data collection. This means
that **local** data analysis won't be available because there is no data
to analyze! Keep in mind that Firefox Health Report can be useful even
if it's not submitting data to remote servers!

Logging
-------

The following preferences allow you to control the logging behavior of
Firefox Health Report.

logging.consoleEnabled
   Whether to write log messages to the web console. This is true by
   default.

logging.consoleLevel
   The minimum log level FHR messages must have to be written to the
   web console. By default, only FHR warnings or errors will be written
   to the web console. During normal/expected operation, no messages of
   this type should be produced.

logging.dumpEnabled
   Whether to write log messages via ``dump()``. If true, FHR will write
   messages to stdout/stderr.

   This is typically only enabled when developing FHR.

logging.dumpLevel
   The minimum log level messages must have to be written via
   ``dump()``.

State
-----

currentDaySubmissionFailureCount
   How many submission failures the client has encountered while
   attempting to upload the most recent document.

lastDataSubmissionFailureTime
   The time of the last failed document upload.

lastDataSubmissionRequestedTime
   The time of the last document upload attempt.

lastDataSubmissionSuccessfulTime
   The time of the last successful document upload.

nextDataSubmissionTime
   The time the next data submission is scheduled for. FHR will not
   attempt to upload a new document before this time.

pendingDeleteRemoteData
   Whether the client currently has a pending request to delete remote
   data. If true, the client will attempt to delete all remote data
   before an upload is performed.

FHR stores various state in preferences.

Registering Providers
=====================

Firefox Health Report providers are registered via the category manager.
See ``HealthReportComponents.manifest`` for providers defined in this
directory.

Essentially, the category manager receives the name of a JS type and the
URI of a sys.mjs to import that exports this symbol. At run-time, the
providers registered in the category manager are instantiated.

Providers are registered via the category manager to make registration
simple and less prone to errors. Any XPCOM component can create a
category manager entry. Therefore, new data providers can be added
without having to touch core Firefox Health Report code. Additionally,
category manager registration means providers are more likely to be
registered on FHR's terms, when it wants. If providers were registered
in code at application run-time, there would be the risk of other
components prematurely instantiating FHR (causing a performance hit if
performed at an inopportune time) or semi-complicated code around
observers or listeners. Category manager entries are only 1 line per
provider and leave FHR in control: they are simple and safe.

Document Generation and Lifecycle
=================================

FHR will attempt to submit a JSON document containing data every 24 wall
clock hours.

At upload time, FHR will query the database for **all** information from
the last 180 days and assemble this data into a JSON document. We
attempt to upload this JSON document with a client-generated UUID to the
configured server.

Before we attempt upload, the generated UUID is stored in the JSON state
file on local disk. At this point, the client assumes the document with
that UUID has been successfully stored on the server.

If the client is aware of other document UUIDs that presumably exist on
the server, those UUIDs are sent with the upload request so the client
can request those UUIDs be deleted. This helps ensure that each client
only has 1 document/UUID on the server at any one time.

Importance of Persisting UUIDs
------------------------------

The choices of how, where, and when document UUIDs are stored and updated
are very important. One should not attempt to change things unless she
has a very detailed understanding of why things are the way they are.

The client is purposefully very conservative about forgetting about
generated UUIDs. In other words, once a UUID is generated, the client
deliberately holds on to that UUID until it's very confident that UUID
is no longer stored on the server. The reason we do this is because
*orphaned* documents/UUIDs on the server can lead to faulty analysis,
such as over-reporting the number of Firefox installs that stop being
used.

When uploading a new UUID, we update the state and save the state file
to disk *before* an upload attempt because if the upload succeeds but
the response never makes it back to the client, we want the client to
know about the uploaded UUID so it can delete it later to prevent an
orphan.

We maintain a list of UUIDs locally (not simply the last UUID) because
multiple upload attempts could fail the same way as the previous
paragraph describes and we have no way of knowing which (if any)
actually succeeded. The safest approach is to assume every document
produced managed to get uploaded some how.

We store the UUIDs on a file on disk and not anywhere else because we
want storage to be robust. We originally stored UUIDs in preferences,
which only flush to disk periodically. Writes to preferences were
apparently getting lost. We switched to writing directly to files to
eliminate this window.
