diff options
author | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-11 02:23:44 +0000 |
---|---|---|
committer | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-11 02:23:44 +0000 |
commit | 897b2627d88109e5280e20b6658d702b43468617 (patch) | |
tree | 76271ef33d000294ba9ce7b6d2b6e1aec94da610 | |
parent | 943d8120c51e2ed0146d85a15298d7fa30f316e0 (diff) | |
download | chromium_src-897b2627d88109e5280e20b6658d702b43468617.zip chromium_src-897b2627d88109e5280e20b6658d702b43468617.tar.gz chromium_src-897b2627d88109e5280e20b6658d702b43468617.tar.bz2 |
Add support for uploading UMA metrics data from ChromeFrame. Added support for tracking chrome frame crash metrics via
2 new counters which track successful navigations and crashes. These counters are persisted in the registry under
HKCU\Software\Google\ChromeFrameMetrics.
Any other histogram data like AutomationServer launch time, IE versions etc are simply dropped if IE is shutdown before
they are sent out. The metrics data is uploaded on similar lines as Chrome.
Bug=46057
Review URL: http://codereview.chromium.org/2714003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49493 0039d316-1c4b-4281-b951-d872f2087c98
-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_ |