summaryrefslogtreecommitdiffstats
path: root/chrome_frame/metrics_service.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome_frame/metrics_service.cc')
-rw-r--r--chrome_frame/metrics_service.cc467
1 files changed, 467 insertions, 0 deletions
diff --git a/chrome_frame/metrics_service.cc b/chrome_frame/metrics_service.cc
new file mode 100644
index 0000000..57fdf6e
--- /dev/null
+++ b/chrome_frame/metrics_service.cc
@@ -0,0 +1,467 @@
+// Copyright (c) 2010 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.
+
+
+//------------------------------------------------------------------------------
+// Description of the life cycle of a instance of MetricsService.
+//
+// OVERVIEW
+//
+// A MetricsService instance is created at ChromeFrame startup in
+// the IE process. It is the central controller for the UMA log data.
+// Its major job is to manage logs, prepare them for transmission.
+// Currently only histogram data is tracked in log. When MetricsService
+// prepares log for submission it snapshots the current stats of histograms,
+// translates log to XML. Transmission includes submitting a compressed log
+// as data in a URL-get, and is performed using functionality provided by
+// Urlmon
+// The actual transmission is performed using a windows timer procedure which
+// basically means that the thread on which the MetricsService object is
+// instantiated needs a message pump. Also on IE7 where every tab is created
+// on its own thread we would have a case where the timer procedures can
+// compete for sending histograms.
+//
+// When preparing log for submission we acquire a list of all local histograms
+// that have been flagged for upload to the UMA server.
+//
+// When ChromeFrame shuts down, there will typically be a fragment of an ongoing
+// log that has not yet been transmitted. Currently this data is ignored.
+//
+// With the above overview, we can now describe the state machine's various
+// stats, based on the State enum specified in the state_ member. Those states
+// are:
+//
+// INITIALIZED, // Constructor was called.
+// ACTIVE, // Accumalating log data.
+// STOPPED, // Service has stopped.
+//
+//-----------------------------------------------------------------------------
+
+#include "chrome_frame/metrics_service.h"
+
+#include <windows.h>
+#include <objbase.h>
+
+#if defined(USE_SYSTEM_LIBBZ2)
+#include <bzlib.h>
+#else
+#include "third_party/bzip2/bzlib.h"
+#endif
+
+#include "base/file_version_info.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/app/chrome_version_info.h"
+#include "chrome/installer/util/browser_distribution.h"
+#include "chrome/installer/util/chrome_frame_distribution.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "chrome_frame/bind_status_callback_impl.h"
+#include "chrome_frame/crash_metrics.h"
+#include "chrome_frame/utils.h"
+#include "net/base/upload_data.h"
+#include "chrome_frame/urlmon_bind_status_callback.h"
+
+using base::Time;
+using base::TimeDelta;
+
+static const char kMetricsType[] =
+ "Content-Type: application/vnd.mozilla.metrics.bz2\r\n";
+
+// The delay, in seconds, after startup before sending the first log message.
+static const int kInitialInterlogDuration = 60; // one minute
+
+static const int kUMAUploadTimeoutMilliSeconds = 30000;
+
+base::LazyInstance<base::ThreadLocalPointer<MetricsService> >
+ MetricsService::g_metrics_instance_(base::LINKER_INITIALIZED);
+
+extern base::LazyInstance<StatisticsRecorder> g_statistics_recorder_;
+
+// This class provides functionality to upload the ChromeFrame UMA data to the
+// server. An instance of this class is created whenever we have data to be
+// uploaded to the server.
+class ChromeFrameMetricsDataUploader : public BSCBImpl {
+ public:
+ ChromeFrameMetricsDataUploader()
+ : cache_stream_(NULL),
+ upload_data_size_(0) {
+ DLOG(INFO) << __FUNCTION__;
+ }
+
+ ~ChromeFrameMetricsDataUploader() {
+ DLOG(INFO) << __FUNCTION__;
+ }
+
+ static HRESULT ChromeFrameMetricsDataUploader::UploadDataHelper(
+ const std::string& upload_data) {
+ CComObject<ChromeFrameMetricsDataUploader>* data_uploader = NULL;
+ CComObject<ChromeFrameMetricsDataUploader>::CreateInstance(&data_uploader);
+ DCHECK(data_uploader != NULL);
+
+ data_uploader->AddRef();
+ HRESULT hr = data_uploader->UploadData(upload_data);
+ if (FAILED(hr)) {
+ DLOG(ERROR) << "Failed to initialize ChromeFrame UMA data uploader: Err"
+ << hr;
+ }
+ data_uploader->Release();
+ return hr;
+ }
+
+ HRESULT UploadData(const std::string& upload_data) {
+ if (upload_data.empty()) {
+ NOTREACHED() << "Invalid upload data";
+ return E_INVALIDARG;
+ }
+
+ DCHECK(cache_stream_.get() == NULL);
+
+ upload_data_size_ = upload_data.size() + 1;
+
+ HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, cache_stream_.Receive());
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to create stream. Error:"
+ << hr;
+ return hr;
+ }
+
+ DCHECK(cache_stream_.get());
+
+ unsigned long written = 0;
+ cache_stream_->Write(upload_data.c_str(), upload_data_size_, &written);
+ DCHECK(written == upload_data_size_);
+
+ RewindStream(cache_stream_);
+
+ BrowserDistribution* dist = ChromeFrameDistribution::GetDistribution();
+ server_url_ = dist->GetStatsServerURL();
+ DCHECK(!server_url_.empty());
+
+ hr = CreateURLMoniker(NULL, server_url_.c_str(),
+ upload_moniker_.Receive());
+ if (FAILED(hr)) {
+ DLOG(ERROR) << "Failed to create url moniker for url:"
+ << server_url_.c_str()
+ << " Error:"
+ << hr;
+ } else {
+ ScopedComPtr<IBindCtx> context;
+ hr = CreateAsyncBindCtx(0, this, NULL, context.Receive());
+ DCHECK(SUCCEEDED(hr));
+ DCHECK(context);
+
+ ScopedComPtr<IStream> stream;
+ hr = upload_moniker_->BindToStorage(
+ context, NULL, IID_IStream,
+ reinterpret_cast<void**>(stream.Receive()));
+ if (FAILED(hr)) {
+ NOTREACHED();
+ DLOG(ERROR) << "Failed to bind to upload data moniker. Error:"
+ << hr;
+ }
+ }
+ return hr;
+ }
+
+ STDMETHOD(BeginningTransaction)(LPCWSTR url, LPCWSTR headers, DWORD reserved,
+ LPWSTR* additional_headers) {
+ std::string new_headers;
+ new_headers = StringPrintf("Content-Length: %s\r\n",
+ Int64ToString(upload_data_size_).c_str());
+ new_headers += kMetricsType;
+
+ *additional_headers = reinterpret_cast<wchar_t*>(
+ CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
+
+ lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
+ new_headers.size());
+ return BSCBImpl::BeginningTransaction(url, headers, reserved,
+ additional_headers);
+ }
+
+ STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) {
+ if ((bind_info == NULL) || (bind_info->cbSize == 0) ||
+ (bind_flags == NULL))
+ return E_INVALIDARG;
+
+ *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
+ // Bypass caching proxies on POSTs and PUTs and avoid writing responses to
+ // these requests to the browser's cache
+ *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_PRAGMA_NO_CACHE;
+
+ DCHECK(cache_stream_.get());
+
+ // Initialize the STGMEDIUM.
+ memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
+ bind_info->grfBindInfoF = 0;
+ bind_info->szCustomVerb = NULL;
+ bind_info->dwBindVerb = BINDVERB_POST;
+ bind_info->stgmedData.tymed = TYMED_ISTREAM;
+ bind_info->stgmedData.pstm = cache_stream_.get();
+ bind_info->stgmedData.pstm->AddRef();
+ return BSCBImpl::GetBindInfo(bind_flags, bind_info);
+ }
+
+ STDMETHOD(OnResponse)(DWORD response_code, LPCWSTR response_headers,
+ LPCWSTR request_headers, LPWSTR* additional_headers) {
+ DLOG(INFO) << __FUNCTION__ << " headers: \n" << response_headers;
+ return BSCBImpl::OnResponse(response_code, response_headers,
+ request_headers, additional_headers);
+ }
+
+ private:
+ std::wstring server_url_;
+ size_t upload_data_size_;
+ ScopedComPtr<IStream> cache_stream_;
+ ScopedComPtr<IMoniker> upload_moniker_;
+};
+
+MetricsService* MetricsService::GetInstance() {
+ if (g_metrics_instance_.Pointer()->Get())
+ return g_metrics_instance_.Pointer()->Get();
+
+ g_metrics_instance_.Pointer()->Set(new MetricsService);
+ return g_metrics_instance_.Pointer()->Get();
+}
+
+MetricsService::MetricsService()
+ : recording_active_(false),
+ reporting_active_(false),
+ user_permits_upload_(false),
+ state_(INITIALIZED),
+ thread_(NULL) {
+}
+
+MetricsService::~MetricsService() {
+ SetRecording(false);
+ if (pending_log_) {
+ delete pending_log_;
+ pending_log_ = NULL;
+ }
+ if (current_log_) {
+ delete current_log_;
+ current_log_ = NULL;
+ }
+}
+
+void MetricsService::InitializeMetricsState() {
+ DCHECK(state_ == INITIALIZED);
+
+ thread_ = PlatformThread::CurrentId();
+
+ user_permits_upload_ = GoogleUpdateSettings::GetCollectStatsConsent();
+ // Update session ID
+ session_id_ = CrashMetricsReporter::GetInstance()->IncrementMetric(
+ CrashMetricsReporter::SESSION_ID);
+
+ // Ensure that an instance of the StatisticsRecorder object is created.
+ g_statistics_recorder_.Get();
+
+ CrashMetricsReporter::GetInstance()->set_active(true);
+}
+
+// static
+void MetricsService::Start() {
+ if (GetInstance()->state_ == ACTIVE)
+ return;
+
+ GetInstance()->InitializeMetricsState();
+ GetInstance()->SetRecording(true);
+ GetInstance()->SetReporting(true);
+}
+
+// static
+void MetricsService::Stop() {
+ GetInstance()->SetReporting(false);
+ GetInstance()->SetRecording(false);
+}
+
+void MetricsService::SetRecording(bool enabled) {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+ if (enabled == recording_active_)
+ return;
+
+ if (enabled) {
+ if (client_id_.empty()) {
+ client_id_ = GenerateClientID();
+ // Save client id somewhere.
+ }
+ StartRecording();
+
+ } else {
+ state_ = STOPPED;
+ }
+ recording_active_ = enabled;
+}
+
+// static
+std::string MetricsService::GenerateClientID() {
+ const int kGUIDSize = 39;
+
+ GUID guid;
+ HRESULT guid_result = CoCreateGuid(&guid);
+ DCHECK(SUCCEEDED(guid_result));
+
+ std::wstring guid_string;
+ int result = StringFromGUID2(guid,
+ WriteInto(&guid_string, kGUIDSize), kGUIDSize);
+ DCHECK(result == kGUIDSize);
+ return WideToUTF8(guid_string.substr(1, guid_string.length() - 2));
+}
+
+// static
+void CALLBACK MetricsService::TransmissionTimerProc(HWND window,
+ unsigned int message,
+ unsigned int event_id,
+ unsigned int time) {
+ DLOG(INFO) << "Transmission timer notified";
+ DCHECK(GetInstance() != NULL);
+ GetInstance()->UploadData();
+}
+
+void MetricsService::SetReporting(bool enable) {
+ static const int kChromeFrameMetricsTimerId = 0xFFFFFFFF;
+
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+ if (reporting_active_ != enable) {
+ reporting_active_ = enable;
+ if (reporting_active_) {
+ SetTimer(NULL, kChromeFrameMetricsTimerId, kUMAUploadTimeoutMilliSeconds,
+ reinterpret_cast<TIMERPROC>(TransmissionTimerProc));
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Recording control methods
+
+void MetricsService::StartRecording() {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+ if (current_log_)
+ return;
+
+ current_log_ = new MetricsLogBase(client_id_, session_id_,
+ GetVersionString());
+ if (state_ == INITIALIZED)
+ state_ = ACTIVE;
+}
+
+void MetricsService::StopRecording(bool save_log) {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+ if (!current_log_)
+ return;
+
+ // Put incremental histogram deltas at the end of all log transmissions.
+ // Don't bother if we're going to discard current_log_.
+ if (save_log) {
+ CrashMetricsReporter::GetInstance()->RecordCrashMetrics();
+ RecordCurrentHistograms();
+ }
+
+ if (save_log) {
+ pending_log_ = current_log_;
+ }
+ current_log_ = NULL;
+}
+
+void MetricsService::MakePendingLog() {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+ if (pending_log())
+ return;
+
+ switch (state_) {
+ case INITIALIZED: // We should be further along by now.
+ DCHECK(false);
+ return;
+
+ case ACTIVE:
+ StopRecording(true);
+ StartRecording();
+ break;
+
+ default:
+ DCHECK(false);
+ return;
+ }
+
+ DCHECK(pending_log());
+}
+
+bool MetricsService::TransmissionPermitted() const {
+ // If the user forbids uploading that's their business, and we don't upload
+ // anything.
+ return user_permits_upload_;
+}
+
+std::string MetricsService::PrepareLogSubmissionString() {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+
+ MakePendingLog();
+ DCHECK(pending_log());
+ if (pending_log_== NULL) {
+ return std::string();
+ }
+
+ pending_log_->CloseLog();
+ std::string pending_log_text = pending_log_->GetEncodedLogString();
+ DCHECK(!pending_log_text.empty());
+ DiscardPendingLog();
+ return pending_log_text;
+}
+
+bool MetricsService::UploadData() {
+ DCHECK_EQ(thread_, PlatformThread::CurrentId());
+
+ if (!GetInstance()->TransmissionPermitted())
+ return false;
+
+ static long currently_uploading = 0;
+ if (InterlockedCompareExchange(&currently_uploading, 1, 0)) {
+ DLOG(INFO) << "Contention for uploading metrics data. Backing off";
+ return false;
+ }
+
+ pending_log_text_ = PrepareLogSubmissionString();
+ DCHECK(!pending_log_text_.empty());
+
+ // Allow security conscious users to see all metrics logs that we send.
+ LOG(INFO) << "METRICS LOG: " << pending_log_text_;
+
+ bool ret = true;
+
+ std::string compressed_log;
+ if (!Bzip2Compress(pending_log_text_, &compressed_log)) {
+ NOTREACHED() << "Failed to compress log for transmission.";
+ ret = false;
+ } else {
+ HRESULT hr = ChromeFrameMetricsDataUploader::UploadDataHelper(
+ compressed_log);
+ DCHECK(SUCCEEDED(hr));
+ }
+ DiscardPendingLog();
+
+ currently_uploading = 0;
+ return ret;
+}
+
+// static
+std::string MetricsService::GetVersionString() {
+ scoped_ptr<FileVersionInfo> version_info(
+ chrome_app::GetChromeVersionInfo());
+ if (version_info.get()) {
+ std::string version = WideToUTF8(version_info->product_version());
+ // Add the -F extensions to ensure that UMA data uploaded by ChromeFrame
+ // lands in the ChromeFrame bucket.
+ version += "-F";
+ if (!version_info->is_official_build())
+ version.append("-devel");
+ return version;
+ } else {
+ NOTREACHED() << "Unable to retrieve version string.";
+ }
+
+ return std::string();
+}
+