Home | History | Annotate | Download | only in chrome_frame
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 //------------------------------------------------------------------------------
      6 // Description of the life cycle of a instance of MetricsService.
      7 //
      8 //  OVERVIEW
      9 //
     10 // A MetricsService instance is created at ChromeFrame startup in
     11 // the IE process. It is the central controller for the UMA log data.
     12 // Its major job is to manage logs, prepare them for transmission.
     13 // Currently only histogram data is tracked in log.  When MetricsService
     14 // prepares log for submission it snapshots the current stats of histograms,
     15 // translates log to a protocol buffer.  Transmission includes submitting a
     16 // compressed log as data in a URL-get, and is performed using functionality
     17 // provided by Urlmon
     18 // The actual transmission is performed using a windows timer procedure which
     19 // basically means that the thread on which the MetricsService object is
     20 // instantiated needs a message pump. Also on IE7 where every tab is created
     21 // on its own thread we would have a case where the timer procedures can
     22 // compete for sending histograms.
     23 //
     24 // When preparing log for submission we acquire a list of all local histograms
     25 // that have been flagged for upload to the UMA server.
     26 //
     27 // When ChromeFrame shuts down, there will typically be a fragment of an ongoing
     28 // log that has not yet been transmitted.  Currently this data is ignored.
     29 //
     30 // With the above overview, we can now describe the state machine's various
     31 // stats, based on the State enum specified in the state_ member.  Those states
     32 // are:
     33 //
     34 //    INITIALIZED,      // Constructor was called.
     35 //    ACTIVE,           // Accumalating log data.
     36 //    STOPPED,          // Service has stopped.
     37 //
     38 //-----------------------------------------------------------------------------
     39 
     40 #include "chrome_frame/metrics_service.h"
     41 
     42 #include <atlbase.h>
     43 #include <atlwin.h>
     44 #include <objbase.h>
     45 #include <windows.h>
     46 
     47 #include "base/metrics/statistics_recorder.h"
     48 #include "base/strings/string16.h"
     49 #include "base/strings/string_number_conversions.h"
     50 #include "base/strings/string_util.h"
     51 #include "base/strings/stringprintf.h"
     52 #include "base/strings/utf_string_conversions.h"
     53 #include "base/synchronization/lock.h"
     54 #include "base/win/scoped_comptr.h"
     55 #include "chrome/common/chrome_version_info.h"
     56 #include "chrome/common/metrics/metrics_log_base.h"
     57 #include "chrome/common/metrics/metrics_log_manager.h"
     58 #include "chrome/installer/util/browser_distribution.h"
     59 #include "chrome/installer/util/google_update_settings.h"
     60 #include "chrome_frame/bind_status_callback_impl.h"
     61 #include "chrome_frame/crash_reporting/crash_metrics.h"
     62 #include "chrome_frame/html_utils.h"
     63 #include "chrome_frame/utils.h"
     64 
     65 using base::Time;
     66 using base::TimeDelta;
     67 using base::win::ScopedComPtr;
     68 
     69 // The first UMA upload occurs after this interval.
     70 static const int kInitialUMAUploadTimeoutMilliSeconds = 30000;
     71 
     72 // Default to one UMA upload per 10 mins.
     73 static const int kMinMilliSecondsPerUMAUpload = 600000;
     74 
     75 base::LazyInstance<base::ThreadLocalPointer<MetricsService> >
     76     MetricsService::g_metrics_instance_ = LAZY_INSTANCE_INITIALIZER;
     77 
     78 std::string MetricsService::client_id_;
     79 
     80 base::Lock MetricsService::metrics_service_lock_;
     81 
     82 // This class provides functionality to upload the ChromeFrame UMA data to the
     83 // server. An instance of this class is created whenever we have data to be
     84 // uploaded to the server.
     85 class ChromeFrameMetricsDataUploader : public BSCBImpl {
     86  public:
     87   ChromeFrameMetricsDataUploader()
     88       : cache_stream_(NULL),
     89         upload_data_size_(0) {
     90     DVLOG(1) << __FUNCTION__;
     91   }
     92 
     93   ~ChromeFrameMetricsDataUploader() {
     94     DVLOG(1) << __FUNCTION__;
     95   }
     96 
     97   static HRESULT UploadDataHelper(
     98       const std::string& upload_data,
     99       const std::string& server_url,
    100       const std::string& mime_type) {
    101     CComObject<ChromeFrameMetricsDataUploader>* data_uploader = NULL;
    102     CComObject<ChromeFrameMetricsDataUploader>::CreateInstance(&data_uploader);
    103     DCHECK(data_uploader != NULL);
    104 
    105     data_uploader->AddRef();
    106     HRESULT hr = data_uploader->UploadData(upload_data, server_url, mime_type);
    107     if (FAILED(hr)) {
    108       DLOG(ERROR) << "Failed to initialize ChromeFrame UMA data uploader: Err"
    109                   << hr;
    110     }
    111     data_uploader->Release();
    112     return hr;
    113   }
    114 
    115   HRESULT UploadData(const std::string& upload_data,
    116                      const std::string& server_url,
    117                      const std::string& mime_type) {
    118     if (upload_data.empty()) {
    119       NOTREACHED() << "Invalid upload data";
    120       return E_INVALIDARG;
    121     }
    122 
    123     DCHECK(cache_stream_.get() == NULL);
    124 
    125     upload_data_size_ = upload_data.size() + 1;
    126 
    127     HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, cache_stream_.Receive());
    128     if (FAILED(hr)) {
    129       NOTREACHED() << "Failed to create stream. Error:"
    130                    << hr;
    131       return hr;
    132     }
    133 
    134     DCHECK(cache_stream_.get());
    135 
    136     unsigned long written = 0;
    137     cache_stream_->Write(upload_data.c_str(), upload_data_size_, &written);
    138     DCHECK(written == upload_data_size_);
    139 
    140     RewindStream(cache_stream_);
    141 
    142     server_url_ = ASCIIToWide(server_url);
    143     mime_type_ = mime_type;
    144     DCHECK(!server_url_.empty());
    145     DCHECK(!mime_type_.empty());
    146 
    147     hr = CreateURLMoniker(NULL, server_url_.c_str(),
    148                           upload_moniker_.Receive());
    149     if (FAILED(hr)) {
    150       DLOG(ERROR) << "Failed to create url moniker for url:"
    151                   << server_url_.c_str()
    152                   << " Error:"
    153                   << hr;
    154     } else {
    155       ScopedComPtr<IBindCtx> context;
    156       hr = CreateAsyncBindCtx(0, this, NULL, context.Receive());
    157       DCHECK(SUCCEEDED(hr));
    158       DCHECK(context);
    159 
    160       ScopedComPtr<IStream> stream;
    161       hr = upload_moniker_->BindToStorage(
    162           context, NULL, IID_IStream,
    163           reinterpret_cast<void**>(stream.Receive()));
    164       if (FAILED(hr)) {
    165         NOTREACHED();
    166         DLOG(ERROR) << "Failed to bind to upload data moniker. Error:"
    167                     << hr;
    168       }
    169     }
    170     return hr;
    171   }
    172 
    173   STDMETHOD(BeginningTransaction)(LPCWSTR url, LPCWSTR headers, DWORD reserved,
    174                                   LPWSTR* additional_headers) {
    175     std::string new_headers;
    176     new_headers =
    177         base::StringPrintf(
    178             "Content-Length: %s\r\n"
    179             "Content-Type: %s\r\n"
    180             "%s\r\n",
    181             base::Int64ToString(upload_data_size_).c_str(),
    182             mime_type_.c_str(),
    183             http_utils::GetDefaultUserAgentHeaderWithCFTag().c_str());
    184 
    185     *additional_headers = reinterpret_cast<wchar_t*>(
    186         CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
    187 
    188     lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
    189               new_headers.size());
    190 
    191     return BSCBImpl::BeginningTransaction(url, headers, reserved,
    192                                           additional_headers);
    193   }
    194 
    195   STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) {
    196     if ((bind_info == NULL) || (bind_info->cbSize == 0) ||
    197         (bind_flags == NULL))
    198       return E_INVALIDARG;
    199 
    200     *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
    201     // Bypass caching proxies on POSTs and PUTs and avoid writing responses to
    202     // these requests to the browser's cache
    203     *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_PRAGMA_NO_CACHE;
    204 
    205     DCHECK(cache_stream_.get());
    206 
    207     // Initialize the STGMEDIUM.
    208     memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM));
    209     bind_info->grfBindInfoF = 0;
    210     bind_info->szCustomVerb = NULL;
    211     bind_info->dwBindVerb = BINDVERB_POST;
    212     bind_info->stgmedData.tymed = TYMED_ISTREAM;
    213     bind_info->stgmedData.pstm = cache_stream_.get();
    214     bind_info->stgmedData.pstm->AddRef();
    215     return BSCBImpl::GetBindInfo(bind_flags, bind_info);
    216   }
    217 
    218   STDMETHOD(OnResponse)(DWORD response_code, LPCWSTR response_headers,
    219                         LPCWSTR request_headers, LPWSTR* additional_headers) {
    220     DVLOG(1) << __FUNCTION__ << " headers: \n" << response_headers;
    221     return BSCBImpl::OnResponse(response_code, response_headers,
    222                                 request_headers, additional_headers);
    223   }
    224 
    225  private:
    226   std::wstring server_url_;
    227   std::string mime_type_;
    228   size_t upload_data_size_;
    229   ScopedComPtr<IStream> cache_stream_;
    230   ScopedComPtr<IMoniker> upload_moniker_;
    231 };
    232 
    233 MetricsService* MetricsService::GetInstance() {
    234   if (g_metrics_instance_.Pointer()->Get())
    235     return g_metrics_instance_.Pointer()->Get();
    236 
    237   g_metrics_instance_.Pointer()->Set(new MetricsService);
    238   return g_metrics_instance_.Pointer()->Get();
    239 }
    240 
    241 MetricsService::MetricsService()
    242     : recording_active_(false),
    243       reporting_active_(false),
    244       user_permits_upload_(false),
    245       state_(INITIALIZED),
    246       thread_(NULL),
    247       initial_uma_upload_(true),
    248       transmission_timer_id_(0) {
    249 }
    250 
    251 MetricsService::~MetricsService() {
    252   SetRecording(false);
    253 }
    254 
    255 void MetricsService::InitializeMetricsState() {
    256   DCHECK(state_ == INITIALIZED);
    257 
    258   thread_ = base::PlatformThread::CurrentId();
    259 
    260   user_permits_upload_ = GoogleUpdateSettings::GetCollectStatsConsent();
    261   // Update session ID
    262   session_id_ = CrashMetricsReporter::GetInstance()->IncrementMetric(
    263       CrashMetricsReporter::SESSION_ID);
    264 
    265   base::StatisticsRecorder::Initialize();
    266   CrashMetricsReporter::GetInstance()->set_active(true);
    267 }
    268 
    269 // static
    270 void MetricsService::Start() {
    271   base::AutoLock lock(metrics_service_lock_);
    272 
    273   if (GetInstance()->state_ == ACTIVE)
    274     return;
    275 
    276   GetInstance()->InitializeMetricsState();
    277   GetInstance()->SetRecording(true);
    278   GetInstance()->SetReporting(true);
    279 }
    280 
    281 // static
    282 void MetricsService::Stop() {
    283   base::AutoLock lock(metrics_service_lock_);
    284 
    285   GetInstance()->SetReporting(false);
    286   GetInstance()->SetRecording(false);
    287 }
    288 
    289 void MetricsService::SetRecording(bool enabled) {
    290   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    291   if (enabled == recording_active_)
    292     return;
    293 
    294   if (enabled) {
    295     StartRecording();
    296   } else {
    297     state_ = STOPPED;
    298   }
    299   recording_active_ = enabled;
    300 }
    301 
    302 // static
    303 const std::string& MetricsService::GetClientID() {
    304   // TODO(robertshield): Chrome Frame shouldn't generate a new ID on every run
    305   // as this apparently breaks some assumptions during metric analysis.
    306   // See http://crbug.com/117188
    307   if (client_id_.empty()) {
    308     const int kGUIDSize = 39;
    309 
    310     GUID guid;
    311     HRESULT guid_result = CoCreateGuid(&guid);
    312     DCHECK(SUCCEEDED(guid_result));
    313 
    314     string16 guid_string;
    315     int result = StringFromGUID2(guid,
    316                                  WriteInto(&guid_string, kGUIDSize), kGUIDSize);
    317     DCHECK(result == kGUIDSize);
    318     client_id_ = WideToUTF8(guid_string.substr(1, guid_string.length() - 2));
    319   }
    320   return client_id_;
    321 }
    322 
    323 // static
    324 void CALLBACK MetricsService::TransmissionTimerProc(HWND window,
    325                                                     unsigned int message,
    326                                                     unsigned int event_id,
    327                                                     unsigned int time) {
    328   DVLOG(1) << "Transmission timer notified";
    329   DCHECK(GetInstance() != NULL);
    330   GetInstance()->UploadData();
    331   if (GetInstance()->initial_uma_upload_) {
    332     // If this is the first uma upload by this process then subsequent uma
    333     // uploads should occur once every 10 minutes(default).
    334     GetInstance()->initial_uma_upload_ = false;
    335     DCHECK(GetInstance()->transmission_timer_id_ != 0);
    336     SetTimer(NULL, GetInstance()->transmission_timer_id_,
    337              kMinMilliSecondsPerUMAUpload,
    338              reinterpret_cast<TIMERPROC>(TransmissionTimerProc));
    339   }
    340 }
    341 
    342 void MetricsService::SetReporting(bool enable) {
    343   static const int kChromeFrameMetricsTimerId = 0xFFFFFFFF;
    344 
    345   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    346   if (reporting_active_ != enable) {
    347     reporting_active_ = enable;
    348     if (reporting_active_) {
    349       transmission_timer_id_ =
    350           SetTimer(NULL, kChromeFrameMetricsTimerId,
    351                    kInitialUMAUploadTimeoutMilliSeconds,
    352                    reinterpret_cast<TIMERPROC>(TransmissionTimerProc));
    353     } else {
    354       UploadData();
    355     }
    356   }
    357 }
    358 
    359 //------------------------------------------------------------------------------
    360 // Recording control methods
    361 
    362 void MetricsService::StartRecording() {
    363   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    364   if (log_manager_.current_log())
    365     return;
    366 
    367   MetricsLogBase::LogType log_type = (state_ == INITIALIZED) ?
    368       MetricsLogBase::INITIAL_LOG : MetricsLogBase::ONGOING_LOG;
    369   log_manager_.BeginLoggingWithLog(new MetricsLogBase(GetClientID(),
    370                                                       session_id_,
    371                                                       GetVersionString()),
    372                                    log_type);
    373   if (state_ == INITIALIZED)
    374     state_ = ACTIVE;
    375 }
    376 
    377 void MetricsService::StopRecording(bool save_log) {
    378   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    379   if (!log_manager_.current_log())
    380     return;
    381 
    382   // Put incremental histogram deltas at the end of all log transmissions.
    383   // Don't bother if we're going to discard current_log.
    384   if (save_log) {
    385     CrashMetricsReporter::GetInstance()->RecordCrashMetrics();
    386     RecordCurrentHistograms();
    387   }
    388 
    389   if (save_log) {
    390     log_manager_.FinishCurrentLog();
    391     log_manager_.StageNextLogForUpload();
    392   } else {
    393     log_manager_.DiscardCurrentLog();
    394   }
    395 }
    396 
    397 void MetricsService::MakePendingLog() {
    398   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    399   if (log_manager_.has_staged_log())
    400     return;
    401 
    402   if (state_ != ACTIVE) {
    403     NOTREACHED();
    404     return;
    405   }
    406 
    407   StopRecording(true);
    408   StartRecording();
    409 }
    410 
    411 bool MetricsService::TransmissionPermitted() const {
    412   // If the user forbids uploading that's their business, and we don't upload
    413   // anything.
    414   return user_permits_upload_;
    415 }
    416 
    417 bool MetricsService::UploadData() {
    418   DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
    419 
    420   if (!GetInstance()->TransmissionPermitted())
    421     return false;
    422 
    423   static long currently_uploading = 0;
    424   if (InterlockedCompareExchange(&currently_uploading, 1, 0)) {
    425     DVLOG(1) << "Contention for uploading metrics data. Backing off";
    426     return false;
    427   }
    428 
    429   MakePendingLog();
    430 
    431   bool ret = true;
    432 
    433   if (log_manager_.has_staged_log()) {
    434     HRESULT hr = ChromeFrameMetricsDataUploader::UploadDataHelper(
    435         log_manager_.staged_log_text(), kServerUrl, kMimeType);
    436     DCHECK(SUCCEEDED(hr));
    437     log_manager_.DiscardStagedLog();
    438   } else {
    439     NOTREACHED();
    440     ret = false;
    441   }
    442 
    443   currently_uploading = 0;
    444   return ret;
    445 }
    446 
    447 // static
    448 std::string MetricsService::GetVersionString() {
    449   chrome::VersionInfo version_info;
    450   if (version_info.is_valid()) {
    451     std::string version = version_info.Version();
    452     // Add the -F extensions to ensure that UMA data uploaded by ChromeFrame
    453     // lands in the ChromeFrame bucket.
    454     version += "-F";
    455     if (!version_info.IsOfficialBuild())
    456       version.append("-devel");
    457     return version;
    458   } else {
    459     NOTREACHED() << "Unable to retrieve version string.";
    460   }
    461 
    462   return std::string();
    463 }
    464