diff options
-rw-r--r-- | chrome_frame/bho.cc | 27 | ||||
-rw-r--r-- | chrome_frame/bho.h | 6 | ||||
-rw-r--r-- | chrome_frame/chrome_active_document.cc | 4 | ||||
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 7 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_automation.cc | 17 | ||||
-rw-r--r-- | chrome_frame/crash_metrics.cc | 92 | ||||
-rw-r--r-- | chrome_frame/crash_metrics.h | 65 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/crash_report.cc | 3 | ||||
-rw-r--r-- | chrome_frame/metrics_service.cc | 467 | ||||
-rw-r--r-- | chrome_frame/metrics_service.h | 146 | ||||
-rw-r--r-- | chrome_frame/urlmon_url_request.cc | 25 | ||||
-rw-r--r-- | chrome_frame/utils.cc | 22 | ||||
-rw-r--r-- | chrome_frame/utils.h | 8 |
13 files changed, 860 insertions, 29 deletions
diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc index 6cdb0a6..3033b9a 100644 --- a/chrome_frame/bho.cc +++ b/chrome_frame/bho.cc @@ -14,8 +14,10 @@ #include "base/scoped_variant_win.h" #include "base/string_util.h" #include "chrome_tab.h" // NOLINT +#include "chrome_frame/crash_metrics.h" #include "chrome_frame/extra_system_apis.h" #include "chrome_frame/http_negotiate.h" +#include "chrome_frame/metrics_service.h" #include "chrome_frame/protocol_sink_wrap.h" #include "chrome_frame/urlmon_moniker.h" #include "chrome_frame/utils.h" @@ -48,6 +50,13 @@ _ATL_FUNC_INFO Bho::kNavigateComplete2Info = { } }; +_ATL_FUNC_INFO Bho::kDocumentCompleteInfo = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + Bho::Bho() { } @@ -84,6 +93,7 @@ STDMETHODIMP Bho::SetSite(IUnknown* site) { // information for a URL. AddRef(); RegisterThreadInstance(); + MetricsService::Start(); } else { UnregisterThreadInstance(); Release(); @@ -134,6 +144,23 @@ STDMETHODIMP_(void) Bho::NavigateComplete2(IDispatch* dispatch, VARIANT* url) { DLOG(INFO) << __FUNCTION__; } +STDMETHODIMP_(void) Bho::DocumentComplete(IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; + + ScopedComPtr<IWebBrowser2> web_browser2; + if (dispatch) + web_browser2.QueryFrom(dispatch); + + if (web_browser2) { + VARIANT_BOOL is_top_level = VARIANT_FALSE; + web_browser2->get_TopLevelContainer(&is_top_level); + if (is_top_level) { + CrashMetricsReporter::GetInstance()->IncrementMetric( + CrashMetricsReporter::NAVIGATION_COUNT); + } + } +} + namespace { // See comments in Bho::OnHttpEquiv for details. diff --git a/chrome_frame/bho.h b/chrome_frame/bho.h index 466dcdb..bbf6202 100644 --- a/chrome_frame/bho.h +++ b/chrome_frame/bho.h @@ -78,6 +78,8 @@ BEGIN_SINK_MAP(Bho) BeforeNavigate2, &kBeforeNavigate2Info) SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, NavigateComplete2, &kNavigateComplete2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, + DocumentComplete, &kDocumentCompleteInfo) END_SINK_MAP() Bho(); @@ -87,10 +89,13 @@ END_SINK_MAP() // IObjectWithSite STDMETHODIMP SetSite(IUnknown* site); + + // WebBrowser2 event sinks. STDMETHOD(BeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers, VARIANT_BOOL* cancel); STDMETHOD_(void, NavigateComplete2)(IDispatch* dispatch, VARIANT* url); + STDMETHOD_(void, DocumentComplete)(IDispatch* dispatch, VARIANT* url); // mshtml sends an IOleCommandTarget::Exec of OLECMDID_HTTPEQUIV // (and OLECMDID_HTTPEQUIV_DONE) as soon as it parses a meta tag. @@ -115,6 +120,7 @@ END_SINK_MAP() static _ATL_FUNC_INFO kBeforeNavigate2Info; static _ATL_FUNC_INFO kNavigateComplete2Info; + static _ATL_FUNC_INFO kDocumentCompleteInfo; }; #endif // CHROME_FRAME_BHO_H_ diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc index fda2d5b..70b1656 100644 --- a/chrome_frame/chrome_active_document.cc +++ b/chrome_frame/chrome_active_document.cc @@ -38,6 +38,7 @@ #include "chrome/test/automation/tab_proxy.h" #include "chrome_frame/bho.h" #include "chrome_frame/bind_context_info.h" +#include "chrome_frame/crash_metrics.h" #include "chrome_frame/utils.h" const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab"; @@ -609,6 +610,9 @@ void ChromeActiveDocument::OnDidNavigate(int tab_handle, ", Type: " << nav_info.navigation_type << ", Relative Offset: " << nav_info.relative_offset << ", Index: " << nav_info.navigation_index; + CrashMetricsReporter::GetInstance()->IncrementMetric( + CrashMetricsReporter::CHROME_FRAME_NAVIGATION_COUNT); + // This could be NULL if the active document instance is being destroyed. if (!m_spInPlaceSite) { DLOG(INFO) << __FUNCTION__ << "m_spInPlaceSite is NULL. Returning"; diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index 54c5840..3bb4fa9 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -588,7 +588,8 @@ '../chrome/chrome.gyp:common', '../chrome/chrome.gyp:utility', '../build/temp_gyp/googleurl.gyp:googleurl', - + '../third_party/libxml/libxml.gyp:libxml', + '../third_party/bzip2/bzip2.gyp:bzip2', ], 'sources': [ 'bho.cc', @@ -620,6 +621,8 @@ 'com_message_event.h', 'com_type_info_holder.cc', 'com_type_info_holder.h', + 'crash_metrics.cc', + 'crash_metrics.h', 'delete_chrome_history.cc', 'delete_chrome_history.h', 'exception_barrier.cc', @@ -633,6 +636,8 @@ 'http_negotiate.h', 'iids.cc', 'in_place_menu.h', + 'metrics_service.cc', + 'metrics_service.h', 'module_utils.cc', 'module_utils.h', 'ole_document_impl.h', diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc index ee1ed5e..9deaeb5 100644 --- a/chrome_frame/chrome_frame_automation.cc +++ b/chrome_frame/chrome_frame_automation.cc @@ -24,6 +24,7 @@ #include "chrome/test/automation/tab_proxy.h" #include "chrome_frame/chrome_launcher_utils.h" #include "chrome_frame/custom_sync_call_context.h" +#include "chrome_frame/crash_metrics.h" #include "chrome_frame/utils.h" #ifdef NDEBUG @@ -250,9 +251,13 @@ void ProxyFactory::GetAutomationServer( entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &ProxyFactory::CreateProxy, entry, params, delegate)); - entry->thread->message_loop()->PostDelayedTask(FROM_HERE, - NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry), - uma_send_interval_); + // IE uses the chrome frame provided UMA data uploading scheme. NPAPI + // continues to use Chrome to upload UMA data. + if (!CrashMetricsReporter::GetInstance()->active()) { + entry->thread->message_loop()->PostDelayedTask(FROM_HERE, + NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry), + uma_send_interval_); + } } void ProxyFactory::CreateProxy(ProxyFactory::ProxyCacheEntry* entry, @@ -420,6 +425,12 @@ void ProxyFactory::ReleaseProxy(ProxyCacheEntry* entry, Singleton<ProxyFactory> g_proxy_factory; void ProxyFactory::SendUMAData(ProxyCacheEntry* proxy_entry) { + // IE uses the chrome frame provided UMA data uploading scheme. NPAPI + // continues to use Chrome to upload UMA data. + if (CrashMetricsReporter::GetInstance()->active()) { + return; + } + if (!proxy_entry) { NOTREACHED() << __FUNCTION__ << " Invalid proxy entry"; return; diff --git a/chrome_frame/crash_metrics.cc b/chrome_frame/crash_metrics.cc new file mode 100644 index 0000000..8b1a97a --- /dev/null +++ b/chrome_frame/crash_metrics.cc @@ -0,0 +1,92 @@ +// 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. + +#include "chrome_frame/crash_metrics.h" + +#include "base/histogram.h" +#include "base/registry.h" +#include "chrome_frame/utils.h" + +static const wchar_t kChromeFrameMetricsKey[] = + L"Software\\Google\\ChromeFrameMetrics"; + +base::LazyInstance<CrashMetricsReporter> + g_crash_metrics_instance_(base::LINKER_INITIALIZED); + +wchar_t* CrashMetricsReporter::g_metric_names[LAST_METRIC] = { + L"navigationcount", + L"crashcount", + L"chrome_frame_navigationcount", + L"sessionid", +}; + +CrashMetricsReporter* CrashMetricsReporter::GetInstance() { + return &g_crash_metrics_instance_.Get(); +} + +bool CrashMetricsReporter::SetMetric(Metric metric, int value) { + DCHECK(metric >= NAVIGATION_COUNT && metric <= LAST_METRIC); + + RegKey metric_key; + if (metric_key.Create(HKEY_CURRENT_USER, kChromeFrameMetricsKey, + KEY_SET_VALUE)) { + if (metric_key.WriteValue(g_metric_names[metric], value)) { + return true; + } else { + DLOG(ERROR) << "Failed to read ChromeFrame metric:" + << g_metric_names[metric]; + } + } else { + DLOG(ERROR) << "Failed to create ChromeFrame metrics key"; + } + return false; +} + +int CrashMetricsReporter::GetMetric(Metric metric) { + DCHECK(metric >= NAVIGATION_COUNT && metric <= LAST_METRIC); + + int ret = 0; + RegKey metric_key; + if (metric_key.Open(HKEY_CURRENT_USER, kChromeFrameMetricsKey, + KEY_QUERY_VALUE)) { + int value = 0; + if (metric_key.ReadValueDW(g_metric_names[metric], + reinterpret_cast<DWORD*>(&value))) { + ret = value; + } + } + return ret; +} + +int CrashMetricsReporter::IncrementMetric(Metric metric) { + DCHECK(metric >= NAVIGATION_COUNT && metric <= LAST_METRIC); + int metric_value = GetMetric(metric); + metric_value++; + SetMetric(metric, metric_value); + return metric_value; +} + +void CrashMetricsReporter::RecordCrashMetrics() { + int navigation_count = GetMetric(NAVIGATION_COUNT); + if (navigation_count > 0) { + THREAD_SAFE_UMA_HISTOGRAM_COUNTS("ChromeFrame.HostNavigationCount", + navigation_count); + SetMetric(NAVIGATION_COUNT, 0); + } + + int chrome_frame_navigation_count = GetMetric(CHROME_FRAME_NAVIGATION_COUNT); + if (chrome_frame_navigation_count > 0) { + THREAD_SAFE_UMA_HISTOGRAM_COUNTS("ChromeFrame.CFNavigationCount", + chrome_frame_navigation_count); + SetMetric(CHROME_FRAME_NAVIGATION_COUNT, 0); + } + + int crash_count = GetMetric(CRASH_COUNT); + if (crash_count > 0) { + THREAD_SAFE_UMA_HISTOGRAM_COUNTS("ChromeFrame.HostCrashCount", + crash_count); + SetMetric(CRASH_COUNT, 0); + } +} + diff --git a/chrome_frame/crash_metrics.h b/chrome_frame/crash_metrics.h new file mode 100644 index 0000000..f0812c6 --- /dev/null +++ b/chrome_frame/crash_metrics.h @@ -0,0 +1,65 @@ +// 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. + +// This file defines a service that collects information about the user +// experience in order to help improve future versions of the app. + +#ifndef CHROME_FRAME_CRASH_METRICS_H_ +#define CHROME_FRAME_CRASH_METRICS_H_ + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/thread_local.h" + +// This class provides functionality to track counters like successful page +// loads in the host browser, total number of crashes, page loads in chrome +// frame. +class CrashMetricsReporter { + public: + enum Metric { + NAVIGATION_COUNT, + CRASH_COUNT, + CHROME_FRAME_NAVIGATION_COUNT, + SESSION_ID, + LAST_METRIC, + }; + // Returns the global instance of this class. + static CrashMetricsReporter* GetInstance(); + + // The following function pair return/set/increment the specified user + // metrics value from the registry. These values are set under the + // following key:- + // HKCU\Software\\Google\\ChromeFrame\\UserMetrics + int GetMetric(Metric metric); + bool SetMetric(Metric metric, int value); + int IncrementMetric(Metric metric); + + // Records the crash metrics counters like navigation count, crash count. + void RecordCrashMetrics(); + + bool active() const { + return active_; + } + + void set_active(bool active) { + active_ = active; + } + + private: + friend struct base::DefaultLazyInstanceTraits<CrashMetricsReporter>; + + CrashMetricsReporter() + : active_(false) {} + virtual ~CrashMetricsReporter() {} + + // Indicates whether the crash metrics reporter instance is active. + bool active_; + + static wchar_t* g_metric_names[LAST_METRIC]; + + DISALLOW_COPY_AND_ASSIGN(CrashMetricsReporter); +}; + +#endif // CHROME_FRAME_CRASH_METRICS_H_ + diff --git a/chrome_frame/crash_reporting/crash_report.cc b/chrome_frame/crash_reporting/crash_report.cc index 4589bab..f821320 100644 --- a/chrome_frame/crash_reporting/crash_report.cc +++ b/chrome_frame/crash_reporting/crash_report.cc @@ -9,6 +9,7 @@ #include "base/basictypes.h" #include "base/lock.h" #include "breakpad/src/client/windows/handler/exception_handler.h" +#include "chrome_frame/crash_metrics.h" // TODO(joshia): factor out common code with chrome used for crash reporting const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; @@ -172,6 +173,8 @@ bool ShutdownVectoredCrashReporting() { bool WriteMinidumpForException(EXCEPTION_POINTERS* p) { AutoLock lock(g_breakpad_lock); + CrashMetricsReporter::GetInstance()->IncrementMetric( + CrashMetricsReporter::CRASH_COUNT); bool success = false; if (g_breakpad) { success = g_breakpad->WriteMinidumpForException(p); 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(); +} + diff --git a/chrome_frame/metrics_service.h b/chrome_frame/metrics_service.h new file mode 100644 index 0000000..766a423 --- /dev/null +++ b/chrome_frame/metrics_service.h @@ -0,0 +1,146 @@ +// 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. + +// This file defines a service that collects information about the user +// experience in order to help improve future versions of the app. + +#ifndef CHROME_FRAME_METRICS_METRICS_SERVICE_H_ +#define CHROME_FRAME_METRICS_METRICS_SERVICE_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/histogram.h" +#include "base/lazy_instance.h" +#include "base/scoped_ptr.h" +#include "base/thread_local.h" +#include "chrome/common/metrics_helpers.h" + +// TODO(ananta) +// Refactor more common code from chrome/browser/metrics/metrics_service.h into +// the MetricsServiceBase class. +class MetricsService : public MetricsServiceBase { + public: + static MetricsService* GetInstance(); + // 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. + static void Start(); + static void Stop(); + // Set up client ID, session ID, etc. + void InitializeMetricsState(); + + private: + MetricsService(); + virtual ~MetricsService(); + // 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. + ACTIVE, // Accumalating log data + STOPPED, // Service has stopped + }; + + // Maintain a map of histogram names to the sample stats we've sent. + typedef std::map<std::string, Histogram::SampleSet> LoggedSampleMap; + + // 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). + // Return value "true" indicates setting was definitively set as requested). + // Return value of "false" indicates that the enable state is effectively + // stuck in the other logical setting. + // Google Update maintains the authoritative preference in the registry, so + // the caller *might* not be able to actually change the setting. + // It is always possible to set this to at least one value, which matches the + // current value reported by querying Google Update. + 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); + + // Generates a new client ID to use to identify self to metrics server. + static std::string GenerateClientID(); + + // ChromeFrame UMA data is uploaded when this timer proc gets invoked. + static void CALLBACK TransmissionTimerProc(HWND window, unsigned int message, + unsigned int event_id, + unsigned int time); + + // Called to start recording user experience metrics. + // Constructs a new, empty current_log_. + void StartRecording(); + + // Called to stop recording user experience metrics. + void StopRecording(bool save_log); + + // 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 and by + // the user whether the pending_log_ should be sent or discarded. Called by + // TryToStartTransmission. + bool TransmissionPermitted() const; + + bool recording_active() const { + return recording_active_; + } + + bool reporting_active() const { + return reporting_active_; + } + + // Convert pending_log_ to XML in pending_log_text_ for transmission. + std::string PrepareLogSubmissionString(); + + // Upload pending data to the server by converting it to XML and compressing + // it. Returns true on success. + bool UploadData(); + + // Get the current version of the application as a string. + static std::string GetVersionString(); + + // 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_; + + // Coincides with the check box in options window that lets the user control + // whether to upload. + bool user_permits_upload_; + + // The progession of states made by the browser are recorded in the following + // state. + State state_; + + // 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_; + + // A number that identifies the how many times the app has been launched. + int session_id_; + + static base::LazyInstance<base::ThreadLocalPointer<MetricsService> > + g_metrics_instance_; + + PlatformThreadId thread_; + + DISALLOW_COPY_AND_ASSIGN(MetricsService); +}; + +#endif // CHROME_FRAME_METRICS_METRICS_SERVICE_H_ + diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc index 093b19f..642a438 100644 --- a/chrome_frame/urlmon_url_request.cc +++ b/chrome_frame/urlmon_url_request.cc @@ -21,31 +21,6 @@ #include "net/http/http_util.h" #include "net/http/http_response_headers.h" -namespace { - -// Reads data from a stream into a string. -HRESULT ReadStream(IStream* stream, size_t size, std::string* data) { - DCHECK(stream); - DCHECK(data); - - DWORD read = 0; - HRESULT hr = stream->Read(WriteInto(data, size + 1), size, &read); - DCHECK(hr == S_OK || hr == S_FALSE || hr == E_PENDING); - if (read) { - data->erase(read); - DCHECK_EQ(read, data->length()); - } else { - data->clear(); - // Return S_FALSE if the underlying stream returned S_OK and zero bytes. - if (hr == S_OK) - hr = S_FALSE; - } - - return hr; -} - -} // end namespace - UrlmonUrlRequest::UrlmonUrlRequest() : pending_read_size_(0), headers_received_(false), diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc index b375dcc..735ec6f 100644 --- a/chrome_frame/utils.cc +++ b/chrome_frame/utils.cc @@ -1191,3 +1191,25 @@ std::string Bscf2Str(DWORD flags) { return s; #undef ADD_BSCF_FLAG } + +// Reads data from a stream into a string. +HRESULT ReadStream(IStream* stream, size_t size, std::string* data) { + DCHECK(stream); + DCHECK(data); + + DWORD read = 0; + HRESULT hr = stream->Read(WriteInto(data, size + 1), size, &read); + DCHECK(hr == S_OK || hr == S_FALSE || hr == E_PENDING); + if (read) { + data->erase(read); + DCHECK_EQ(read, data->length()); + } else { + data->clear(); + // Return S_FALSE if the underlying stream returned S_OK and zero bytes. + if (hr == S_OK) + hr = S_FALSE; + } + + return hr; +} + diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index c60dfab..5f25e00 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -393,6 +393,11 @@ extern Lock g_ChromeFrameHistogramLock; UMA_HISTOGRAM_TIMES(name, sample); \ } +#define THREAD_SAFE_UMA_HISTOGRAM_COUNTS(name, sample) { \ + AutoLock lock(g_ChromeFrameHistogramLock); \ + UMA_HISTOGRAM_COUNTS(name, sample); \ +} + // Fired when we want to notify IE about privacy changes. #define WM_FIRE_PRIVACY_CHANGE_NOTIFICATION (WM_APP + 1) @@ -456,4 +461,7 @@ std::string BindStatus2Str(ULONG bind_status); std::string PiFlags2Str(DWORD flags); std::string Bscf2Str(DWORD flags); +// Reads data from a stream into a string. +HRESULT ReadStream(IStream* stream, size_t size, std::string* data); + #endif // CHROME_FRAME_UTILS_H_ |