diff options
Diffstat (limited to 'chrome_frame/metrics_service.cc')
-rw-r--r-- | chrome_frame/metrics_service.cc | 467 |
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(¤tly_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(); +} + |