// Copyright (c) 2011 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.
// This file defines a service that collects information about the user
// experience in order to help improve future versions of the app.
#ifndef CHROME_BROWSER_METRICS_METRICS_SERVICE_H_
#define CHROME_BROWSER_METRICS_METRICS_SERVICE_H_
#pragma once
#include <map>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/common/metrics_helpers.h"
#include "chrome/common/net/url_fetcher.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/external_metrics.h"
#endif
class BookmarkModel;
class BookmarkNode;
class DictionaryValue;
class ListValue;
class HistogramSynchronizer;
class MetricsLogBase;
class PrefService;
class TemplateURLModel;
namespace webkit {
namespace npapi {
struct WebPluginInfo;
}
}
// Forward declaration of the xmlNode to avoid having tons of gyp files
// needing to depend on the libxml third party lib.
struct _xmlNode;
typedef struct _xmlNode xmlNode;
typedef xmlNode* xmlNodePtr;
class MetricsService : public NotificationObserver,
public URLFetcher::Delegate,
public MetricsServiceBase {
public:
// Used to produce a historgram that keeps track of the status of recalling
// persisted per logs.
enum LogRecallStatus {
RECALL_SUCCESS, // We were able to correctly recall a persisted log.
LIST_EMPTY, // Attempting to recall from an empty list.
LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger().
LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3).
LIST_SIZE_CORRUPTION, // List size is not as expected.
LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString().
CHECKSUM_CORRUPTION, // Failed to verify checksum.
CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using
// GetAsString().
DECODE_FAIL, // Failed to decode log.
END_RECALL_STATUS // Number of bins to use to create the histogram.
};
// TODO(ziadh): This is here temporarily for a side experiment. Remove later
// on.
enum LogStoreStatus {
STORE_SUCCESS, // Successfully presisted log.
ENCODE_FAIL, // Failed to encode log.
COMPRESS_FAIL, // Failed to compress log.
END_STORE_STATUS // Number of bins to use to create the histogram.
};
MetricsService();
virtual ~MetricsService();
// Start/stop the metrics recording and uploading machine. These should be
// used on startup and when the user clicks the checkbox in the prefs.
// StartRecordingOnly starts the metrics recording but not reporting, for use
// in tests only.
void Start();
void StartRecordingOnly();
void Stop();
// At startup, prefs needs to be called with a list of all the pref names and
// types we'll be using.
static void RegisterPrefs(PrefService* local_state);
// Set up notifications which indicate that a user is performing work. This is
// useful to allow some features to sleep, until the machine becomes active,
// such as precluding UMA uploads unless there was recent activity.
static void SetUpNotifications(NotificationRegistrar* registrar,
NotificationObserver* observer);
// Implementation of NotificationObserver
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// Invoked when we get a WM_SESSIONEND. This places a value in prefs that is
// reset when RecordCompletedSessionEnd is invoked.
void RecordStartOfSessionEnd();
// This should be called when the application is shutting down. It records
// that session end was successful.
void RecordCompletedSessionEnd();
// Saves in the preferences if the crash report registration was successful.
// This count is eventually send via UMA logs.
void RecordBreakpadRegistration(bool success);
// Saves in the preferences if the browser is running under a debugger.
// This count is eventually send via UMA logs.
void RecordBreakpadHasDebugger(bool has_debugger);
// Save any unsent logs into a persistent store in a pref. We always do this
// at shutdown, but we can do it as we reduce the list as well.
void StoreUnsentLogs();
#if defined(OS_CHROMEOS)
// Start the external metrics service, which collects metrics from Chrome OS
// and passes them to UMA.
void StartExternalMetrics();
// Records a Chrome OS crash.
void LogChromeOSCrash(const std::string &crash_type);
#endif
bool recording_active() const;
bool reporting_active() const;
private:
// The MetricsService has a lifecycle that is stored as a state.
// See metrics_service.cc for description of this lifecycle.
enum State {
INITIALIZED, // Constructor was called.
INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to complete.
INIT_TASK_DONE, // Waiting for timer to send initial log.
INITIAL_LOG_READY, // Initial log generated, and waiting for reply.
SEND_OLD_INITIAL_LOGS, // Sending unsent logs from previous session.
SENDING_OLD_LOGS, // Sending unsent logs from previous session.
SENDING_CURRENT_LOGS, // Sending standard current logs as they acrue.
};
class InitTask;
class InitTaskComplete;
// Callback to let us know that the init task is done.
void OnInitTaskComplete(
const std::string& hardware_class,
const std::vector<webkit::npapi::WebPluginInfo>& plugins);
// When we start a new version of Chromium (different from our last run), we
// need to discard the old crash stats so that we don't attribute crashes etc.
// in the old version to the current version (via current logs).
// Without this, a common reason to finally start a new version is to crash
// the old version (after an autoupdate has arrived), and so we'd bias
// initial results towards showing crashes :-(.
static void DiscardOldStabilityStats(PrefService* local_state);
// Sets and gets whether metrics recording is active.
// SetRecording(false) also forces a persistent save of logging state (if
// anything has been recorded, or transmitted).
void SetRecording(bool enabled);
// Enable/disable transmission of accumulated logs and crash reports (dumps).
void SetReporting(bool enabled);
// If in_idle is true, sets idle_since_last_transmission to true.
// If in_idle is false and idle_since_last_transmission_ is true, sets
// idle_since_last_transmission to false and starts the timer (provided
// starting the timer is permitted).
void HandleIdleSinceLastTransmission(bool in_idle);
// Set up client ID, session ID, etc.
void InitializeMetricsState();
// Generates a new client ID to use to identify self to metrics server.
static std::string GenerateClientID();
// Schedule the next save of LocalState information. This is called
// automatically by the task that performs each save to schedule the next one.
void ScheduleNextStateSave();
// Save the LocalState information immediately. This should not be called by
// anybody other than the scheduler to avoid doing too many writes. When you
// make a change, call ScheduleNextStateSave() instead.
void SaveLocalState();
// Called to start recording user experience metrics.
// Constructs a new, empty current_log_.
void StartRecording();
// Called to stop recording user experience metrics. The caller takes
// ownership of the resulting MetricsLog object via the log parameter,
// or passes in NULL to indicate that the log should simply be deleted.
void StopRecording(MetricsLogBase** log);
// Deletes pending_log_ and current_log_, and pushes their text into the
// appropriate unsent_log vectors. Called when Chrome shuts down.
void PushPendingLogsToUnsentLists();
// Save the pending_log_text_ persistently in a pref for transmission when we
// next run. Note that IF this text is "too large," we just dicard it.
void PushPendingLogTextToUnsentOngoingLogs();
// Start timer for next log transmission.
void StartLogTransmissionTimer();
// Internal function to collect process memory information.
void LogTransmissionTimerDone();
// Do not call OnMemoryDetailCollectionDone() or
// OnHistogramSynchronizationDone() directly.
// Use StartLogTransmissionTimer() to schedule a call.
void OnMemoryDetailCollectionDone();
void OnHistogramSynchronizationDone();
// Takes whatever log should be uploaded next (according to the state_)
// and makes it the pending log. If pending_log_ is not NULL,
// MakePendingLog does nothing and returns.
void MakePendingLog();
// Determines from state_ and permissions set out by the server whether the
// pending_log_ should be sent or discarded.
bool ServerPermitsTransmission() const;
// Check to see if there are any unsent logs from previous sessions.
bool unsent_logs() const {
return !unsent_initial_logs_.empty() || !unsent_ongoing_logs_.empty();
}
// Record stats, client ID, Session ID, etc. in a special "first" log.
void PrepareInitialLog();
// Pull copies of unsent logs from prefs into instance variables.
void RecallUnsentLogs();
// Decode and verify written pref log data.
static MetricsService::LogRecallStatus RecallUnsentLogsHelper(
const ListValue& list,
std::vector<std::string>* local_list);
// Encode and write list size and checksum for perf log data.
static void StoreUnsentLogsHelper(const std::vector<std::string>& local_list,
const size_t kMaxLocalListSize,
ListValue* list);
// Convert |pending_log_| to XML in |compressed_log_|, and compress it for
// transmission.
void PreparePendingLogText();
// Convert pending_log_ to XML, compress it, and prepare to pass to server.
// Upon return, current_fetch_ should be reset with its upload data set to
// a compressed copy of the pending log.
void PrepareFetchWithPendingLog();
// Implementation of URLFetcher::Delegate. Called after transmission
// completes (either successfully or with failure).
virtual void OnURLFetchComplete(const URLFetcher* source,
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data);
// Called by OnURLFetchComplete to handle the case when the server returned
// a response code not equal to 200.
void HandleBadResponseCode();
// Records a window-related notification.
void LogWindowChange(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// Reads, increments and then sets the specified integer preference.
void IncrementPrefValue(const char* path);
// Reads, increments and then sets the specified long preference that is
// stored as a string.
void IncrementLongPrefsValue(const char* path);
// Records a renderer process crash.
void LogRendererCrash();
// Records an extension renderer process crash.
void LogExtensionRendererCrash();
// Records a renderer process hang.
void LogRendererHang();
// Records that the browser was shut down cleanly.
void LogCleanShutdown();
// Set the value in preferences for the number of bookmarks and folders
// in node. The pref key for the number of bookmarks in num_bookmarks_key and
// the pref key for number of folders in num_folders_key.
void LogBookmarks(const BookmarkNode* node,
const char* num_bookmarks_key,
const char* num_folders_key);
// Sets preferences for the number of bookmarks in model.
void LogBookmarks(BookmarkModel* model);
// Records a child process related notification. These are recorded to an
// in-object buffer because these notifications are sent on page load, and we
// don't want to slow that down.
void LogChildProcessChange(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// Logs keywords specific metrics. Keyword metrics are recorded in the
// profile specific metrics.
void LogKeywords(const TemplateURLModel* url_model);
// Saves plugin-related updates from the in-object buffer to Local State
// for retrieval next time we send a Profile log (generally next launch).
void RecordPluginChanges(PrefService* pref);
// Records state that should be periodically saved, like uptime and
// buffered plugin stability statistics.
void RecordCurrentState(PrefService* pref);
// Logs the initiation of a page load
void LogLoadStarted();
// Records a page load notification.
void LogLoadComplete(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// Checks whether a notification can be logged.
bool CanLogNotification(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
// Sets the value of the specified path in prefs and schedules a save.
void RecordBooleanPrefValue(const char* path, bool value);
NotificationRegistrar registrar_;
// Indicate whether recording and reporting are currently happening.
// These should not be set directly, but by calling SetRecording and
// SetReporting.
bool recording_active_;
bool reporting_active_;
// The variable server_permits_upload_ is set true when the response
// data forbids uploading. This should coinside with the "die roll"
// with probability in the upload tag of the response data came out
// affirmative.
bool server_permits_upload_;
// The progession of states made by the browser are recorded in the following
// state.
State state_;
// Chrome OS hardware class (e.g., hardware qualification ID). This
// class identifies the configured system components such as CPU,
// WiFi adapter, etc. For non Chrome OS hosts, this will be an
// empty string.
std::string hardware_class_;
// The list of plugins which was retrieved on the file thread.
std::vector<webkit::npapi::WebPluginInfo> plugins_;
// The outstanding transmission appears as a URL Fetch operation.
scoped_ptr<URLFetcher> current_fetch_;
// The URL for the metrics server.
std::wstring server_url_;
// The identifier that's sent to the server with the log reports.
std::string client_id_;
// Whether the MetricsService object has received any notifications since
// the last time a transmission was sent.
bool idle_since_last_transmission_;
// A number that identifies the how many times the app has been launched.
int session_id_;
// When logs were not sent during a previous session they are queued to be
// sent instead of currently accumulating logs. We give preference to sending
// our inital log first, then unsent intial logs, then unsent ongoing logs.
// Unsent logs are gathered at shutdown, and save in a persistent pref, one
// log in each string in the following arrays.
// Note that the vector has the oldest logs listed first (early in the
// vector), and we'll discard old logs if we have gathered too many logs.
std::vector<std::string> unsent_initial_logs_;
std::vector<std::string> unsent_ongoing_logs_;
// Maps NavigationControllers (corresponding to tabs) or Browser
// (corresponding to Windows) to a unique integer that we will use to identify
// it. |next_window_id_| is used to track which IDs we have used so far.
typedef std::map<uintptr_t, int> WindowMap;
WindowMap window_map_;
int next_window_id_;
// Buffer of child process notifications for quick access. See
// ChildProcessStats documentation above for more details.
struct ChildProcessStats;
std::map<std::wstring, ChildProcessStats> child_process_stats_buffer_;
ScopedRunnableMethodFactory<MetricsService> log_sender_factory_;
ScopedRunnableMethodFactory<MetricsService> state_saver_factory_;
// Dictionary containing all the profile specific metrics. This is set
// at creation time from the prefs.
scoped_ptr<DictionaryValue> profile_dictionary_;
// The interval between consecutive log transmissions (to avoid hogging the
// outbound network link). This is usually also the duration for which we
// build up a log, but if other unsent-logs from previous sessions exist, we
// quickly transmit those unsent logs while we continue to build a log.
base::TimeDelta interlog_duration_;
// Indicate that a timer for sending the next log has already been queued.
bool timer_pending_;
#if defined(OS_CHROMEOS)
// The external metric service is used to log ChromeOS UMA events.
scoped_refptr<chromeos::ExternalMetrics> external_metrics_;
#endif
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, EmptyLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, SingleElementLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, OverLimitLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, SmallRecoveredListSize);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, RemoveSizeFromLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, CorruptSizeOfLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, CorruptChecksumOfLogList);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdGeneratesAllZeroes);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdGeneratesCorrectly);
FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, ClientIdCorrectlyFormatted);
DISALLOW_COPY_AND_ASSIGN(MetricsService);
};
#endif // CHROME_BROWSER_METRICS_METRICS_SERVICE_H_