blob: e242fae34ef3638cf2268d86ef6d8b3bfe285998 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "components/metrics/metrics_provider.h"
#include "components/metrics/structured/key_data.h"
#include "components/metrics/structured/recorder.h"
#include "components/prefs/persistent_pref_store.h"
#include "components/prefs/pref_store.h"
class JsonPrefStore;
namespace metrics {
namespace structured {
// StructuredMetricsProvider is responsible for filling out the
// |structured_metrics_event| section of the UMA proto. This class should not be
// instantiated except by the ChromeMetricsServiceClient. This class is not
// thread safe and should only be called on the browser UI sequence, because
// calls from the metrics service come on the UI sequence.
// Each structured metrics event is sent with other UMA data, and so is
// associated with the UMA client ID when received by the UMA server. The client
// ID is stripped from the events after they reach the server, and so data at
// rest is not attached to the client ID. However, please note that structured
// events are *not* separated from the client ID at the point of upload from
// the device.
// Currently, the structured metrics system is cros-only and relies on the cros
// cryptohome to store keys and unsent logs, collectively called 'state'. This
// means structured metrics collection cannot begin until a profile eligible
// for metrics collection is added.
// TODO( generalize structured metrics beyond cros.
// Initialization of the StructuredMetricsProvider must wait until a profile is
// added, because state is stored within the profile directory. Initialization
// happens in several steps:
// 1. A StructuredMetricsProvider instance is constructed and owned by the
// MetricsService.
// 2. When recording is enabled, the provider registers itself as an observer of
// Recorder.
// 3. When a profile is added that is eligible for recording,
// ChromeMetricsServiceClient calls Recorder::ProfileAdded, which notifies
// this class.
// 4. This class then begins initialization by asynchronously reading keys and
// unsent logs from a JsonPrefStore within the profile path.
// 5. If the read succeeds, initialization is complete and this class starts
// accepting events to record.
// After initialization, this class accepts events to record from
// StructuredMetricsProvider::OnRecord via Recorder::Record via
// EventBase::Record. These events are not uploaded immediately, and are cached
// in ready-to-upload form.
// On a call to ProvideCurrentSessionData, the cache of unsent logs is added to
// a ChromeUserMetricsExtension for upload, and is then cleared.
class StructuredMetricsProvider : public metrics::MetricsProvider,
public Recorder::Observer,
public PrefStore::Observer {
~StructuredMetricsProvider() override;
StructuredMetricsProvider(const StructuredMetricsProvider&) = delete;
StructuredMetricsProvider& operator=(const StructuredMetricsProvider&) =
friend class Recorder;
friend class StructuredMetricsProviderTest;
// An error delegate called when |storage_| has finished reading prefs from
// disk.
class PrefStoreErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
~PrefStoreErrorDelegate() override;
// PersistentPrefStore::ReadErrorDelegate:
void OnError(PersistentPrefStore::PrefReadError error) override;
// metrics::MetricsProvider:
void OnRecordingEnabled() override;
void OnRecordingDisabled() override;
void ProvideCurrentSessionData(
metrics::ChromeUserMetricsExtension* uma_proto) override;
// Recorder::Observer:
void OnRecord(const EventBase& event) override;
void OnProfileAdded(const base::FilePath& profile_path) override;
// PrefStore::Observer:
void OnInitializationCompleted(bool success) override;
void OnPrefValueChanged(const std::string& key) override {}
// Makes the |storage_| PrefStore flush to disk. Used for flushing any
// modified but not-yet-written data to disk during unit tests.
void CommitPendingWriteForTest();
// Beyond this number of logging events between successive calls to
// ProvideCurrentSessionData, we stop recording events.
static int kMaxEventsPerUpload;
// The basename of the file to store key data and unsent logs. A JsonPrefStore
// is initialized at {profile_path}/{kStorageFileName}.
static char kStorageFileName[];
// Whether the metrics provider has completed initialization. Initialization
// occurs across OnProfileAdded and OnInitializationCompleted. No incoming
// events are recorded until initialization has succeeded.
// Execution is:
// - A profile is added.
// - OnProfileAdded is called, which constructs |storage_| and
// asynchronously reads prefs.
// - OnInitializationCompleted is called once pref reading is complete, which
// sets |initialized_| to true.
// The metrics provider does not handle multiprofile: initialization happens
// only once, for the first-logged-in account aka. primary user.
bool initialized_ = false;
// Tracks the recording state signalled to the metrics provider by
// OnRecordingEnabled and OnRecordingDisabled.
bool recording_enabled_ = false;
// On-device storage within the user's cryptohome for keys and unsent logs.
// This is constructed as part of initialization and is guaranteed to be
// initialized if |initialized_| is true.
// For details of key storage, see key_data.h
// Unsent logs are stored in hashed, ready-to-upload form in the structure:
// logs[i].event
// .metrics[j].name
// .value
scoped_refptr<JsonPrefStore> storage_;
// Storage for all event's keys, and hashing logic for values. This stores
// keys on-disk using the |storage_| JsonPrefStore.
std::unique_ptr<internal::KeyData> key_data_;
base::WeakPtrFactory<StructuredMetricsProvider> weak_factory_{this};
} // namespace structured
} // namespace metrics