| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #ifndef COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_ |
| #define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_ |
| |
| #include <deque> |
| #include <memory> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/files/file_path.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/time/time.h" |
| #include "components/metrics/metrics_provider.h" |
| #include "components/metrics/structured/event.h" |
| #include "components/metrics/structured/external_metrics.h" |
| #include "components/metrics/structured/key_data.h" |
| #include "components/metrics/structured/project_validator.h" |
| #include "components/metrics/structured/recorder.h" |
| |
| namespace metrics::structured { |
| |
| // StructuredMetricsRecorder is responsible for storing and managing the all |
| // Structured Metrics events recorded on-device. 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. |
| // |
| // Initialization of the StructuredMetricsRecorder must wait until a profile is |
| // added, because state is stored within the profile directory. Initialization |
| // happens in several steps: |
| // |
| // 1. A StructuredMetricsRecorder instance is constructed and owned by the |
| // MetricsService. It registers itself as an observer of |
| // metrics::structured::Recorder. |
| // |
| // 2. When a profile is added that is eligible for recording, |
| // ChromeMetricsServiceClient calls Recorder::ProfileAdded, which notifies |
| // this class. |
| // |
| // 3. This class then begins initialization by asynchronously reading keys and |
| // unsent logs from the cryptohome. |
| // |
| // 4. If the read succeeds, initialization is complete and this class starts |
| // accepting events to record. |
| // |
| // After initialization, this class accepts events to record from |
| // StructuredMetricsRecorder::OnRecord via Recorder::Record via |
| // Event::Record. These events are not uploaded immediately, and are cached |
| // in ready-to-upload form. |
| // |
| // On a call to ProvideUmaEventMetrics, the cache of unsent logs is added to |
| // a ChromeUserMetricsExtension for upload, and is then cleared. |
| class StructuredMetricsRecorder : public Recorder::RecorderImpl { |
| public: |
| explicit StructuredMetricsRecorder( |
| base::raw_ptr<metrics::MetricsProvider> system_profile_provider); |
| ~StructuredMetricsRecorder() override; |
| StructuredMetricsRecorder(const StructuredMetricsRecorder&) = delete; |
| StructuredMetricsRecorder& operator=(const StructuredMetricsRecorder&) = |
| delete; |
| |
| virtual void EnableRecording(); |
| virtual void DisableRecording(); |
| |
| void Purge(); |
| |
| bool recording_enabled() const { return recording_enabled_; } |
| |
| void ProvideUmaEventMetrics(ChromeUserMetricsExtension& uma_proto); |
| |
| // Provides event metrics stored in the recorder into |uma_proto|. |
| // |
| // This calls OnIndependentMetrics() to populate |uma_proto| with metadata |
| // fields. |
| void ProvideEventMetrics(ChromeUserMetricsExtension& uma_proto); |
| |
| bool can_provide_metrics() const { |
| return recording_enabled() && is_init_state(InitState::kInitialized); |
| } |
| |
| // Returns pointer to in-memory events. |
| EventsProto* events() { return events_->get(); } |
| |
| protected: |
| // Should only be used for tests. |
| // |
| // TODO(crbug/1350322): Use this ctor to replace existing ctor. |
| StructuredMetricsRecorder( |
| const base::FilePath& device_key_path, |
| base::TimeDelta write_delay, |
| base::raw_ptr<metrics::MetricsProvider> system_profile_provider); |
| |
| PersistentProto<EventsProto>& proto() { return *events_.get(); } |
| |
| private: |
| friend class Recorder; |
| friend class StructuredMetricsProviderTest; |
| friend class StructuredMetricsRecorderTest; |
| friend class StructuredMetricsRecorderHwidTest; |
| friend class TestStructuredMetricsRecorder; |
| friend class TestStructuredMetricsProvider; |
| friend class StructuredMetricsServiceTest; |
| |
| // files that are asynchronously read from disk at startup. When all files |
| // have been read, the provider has been initialized. |
| enum class InitState { |
| kUninitialized = 1, |
| // Set after we observe the recorder, which happens on construction. |
| kProfileAdded = 2, |
| // Set after all key and event files are read from disk. |
| kInitialized = 3, |
| }; |
| |
| bool is_init_state(InitState state) const { return init_state_ == state; } |
| |
| void OnKeyDataInitialized(); |
| void OnRead(ReadStatus status); |
| void OnWrite(WriteStatus status); |
| void OnExternalMetricsCollected(const EventsProto& events); |
| |
| // Recorder::RecorderImpl: |
| void OnProfileAdded(const base::FilePath& profile_path) override; |
| void OnEventRecord(const Event& event) override; |
| void OnReportingStateChanged(bool enabled) override; |
| void OnSystemProfileInitialized() override; |
| absl::optional<int> LastKeyRotation(uint64_t project_name_hash) override; |
| |
| void WriteNowForTest(); |
| void SetExternalMetricsDirForTest(const base::FilePath& dir); |
| |
| // Records events before |init_state_| is kInitialized. |
| void RecordEventBeforeInitialization(const Event& event); |
| |
| // Records |event| to persistent disk to be eventually sent. |
| void RecordEvent(const Event& event); |
| |
| // Populates system profile needed for Structured Metrics. |
| // Independent metric uploads will rely on a SystemProfileProvider |
| // to supply the system profile since ChromeOSMetricsProvider will |
| // not be called to populate the SystemProfile. |
| void ProvideSystemProfile(SystemProfileProto* system_profile); |
| |
| // Hashes events and persists the events to disk. Should be called once |this| |
| // has been initialized. |
| void HashUnhashedEventsAndPersist(); |
| |
| // Checks if |project_name_hash| can be uploaded. |
| bool CanUploadProject(uint64_t project_name_hash) const; |
| |
| // Builds a cache of disallow projects from the Finch controlled variable. |
| void CacheDisallowedProjectsSet(); |
| |
| // Adds a project to the diallowed list for testing. |
| void AddDisallowedProjectForTest(uint64_t project_name_hash); |
| |
| // Beyond this number of logging events between successive calls to |
| // ProvideCurrentSessionData, we stop recording events. |
| static int kMaxEventsPerUpload; |
| |
| // The path used to store per-profile keys. Relative to the user's |
| // cryptohome. This file is created by chromium. |
| static char kProfileKeyDataPath[]; |
| |
| // The path used to store per-device keys. This file is created by tmpfiles.d |
| // on start and has its permissions and ownership set such that it is writable |
| // by chronos. |
| static char kDeviceKeyDataPath[]; |
| |
| // The directory used to store unsent logs. Relative to the user's cryptohome. |
| // This file is created by chromium. |
| static char kUnsentLogsPath[]; |
| |
| // 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 events and keys. |
| // - OnInitializationCompleted is called once reading from disk is complete, |
| // which sets |init_count_| to kInitialized. |
| // |
| // The metrics provider does not handle multiprofile: initialization happens |
| // only once, for the first-logged-in account aka. primary user. |
| // |
| // After a profile is added, three files need to be read from disk: |
| // per-profile keys, per-device keys, and unsent events. |init_count_| tracks |
| // how many of these have been read and, when it reaches 3, we set |
| // |init_state_| to kInitialized. |
| InitState init_state_ = InitState::kUninitialized; |
| int init_count_ = 0; |
| static constexpr int kTargetInitCount = 3; |
| |
| // Tracks the recording state signalled to the metrics provider by |
| // OnRecordingEnabled and OnRecordingDisabled. This is false until |
| // OnRecordingEnabled is called, which sets it true if structured metrics' |
| // feature flag is enabled. |
| bool recording_enabled_ = false; |
| |
| // Set by OnReportingStateChanged if all keys and events should be deleted, |
| // but the files backing that state haven't been initialized yet. If set, |
| // state will be purged upon initialization. |
| bool purge_state_on_init_ = false; |
| |
| // The last time we provided independent metrics. |
| base::Time last_provided_independent_metrics_; |
| |
| // Periodically reports metrics from cros. |
| std::unique_ptr<ExternalMetrics> external_metrics_; |
| |
| // On-device storage within the user's cryptohome for unsent logs. |
| std::unique_ptr<PersistentProto<EventsProto>> events_; |
| |
| // Store for events that were recorded before user/device keys are loaded. |
| std::deque<Event> unhashed_events_; |
| |
| // Storage for all event's keys, and hashing logic for values. This stores |
| // keys on disk. |profile_key_data_| stores keys for per-profile projects, |
| // and |device_key_data_| stores keys for per-device projects. |
| std::unique_ptr<KeyData> profile_key_data_; |
| std::unique_ptr<KeyData> device_key_data_; |
| |
| // Whether the system profile has been initialized. |
| bool system_profile_initialized_ = false; |
| |
| // File path where device keys will be persisted. |
| const base::FilePath device_key_path_; |
| |
| // Delay period for PersistentProto writes. Default value of 1000 ms used if |
| // not specified in ctor. |
| base::TimeDelta write_delay_; |
| |
| // Interface for providing the SystemProfile to metrics. |
| // See chrome/browser/metrics/chrome_metrics_service_client.h |
| base::raw_ptr<metrics::MetricsProvider> system_profile_provider_; |
| |
| // A set of projects that are not allowed to be recorded. This is a cache of |
| // GetDisabledProjects(). |
| base::flat_set<uint64_t> disallowed_projects_; |
| |
| // The number of scans of external metrics that occurred since the last |
| // upload. This is only incremented if events were added by the scan. |
| int external_metrics_scans_ = 0; |
| |
| base::WeakPtrFactory<StructuredMetricsRecorder> weak_factory_{this}; |
| }; |
| } // namespace metrics::structured |
| |
| #endif // COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_ |