| OLD | NEW |
| (Empty) |
| 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_ = base::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, base::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 base::string16 guid_string; | |
| 315 int result = StringFromGUID2(guid, | |
| 316 WriteInto(&guid_string, kGUIDSize), kGUIDSize); | |
| 317 DCHECK(result == kGUIDSize); | |
| 318 client_id_ = | |
| 319 base::WideToUTF8(guid_string.substr(1, guid_string.length() - 2)); | |
| 320 } | |
| 321 return client_id_; | |
| 322 } | |
| 323 | |
| 324 // static | |
| 325 void CALLBACK MetricsService::TransmissionTimerProc(HWND window, | |
| 326 unsigned int message, | |
| 327 unsigned int event_id, | |
| 328 unsigned int time) { | |
| 329 DVLOG(1) << "Transmission timer notified"; | |
| 330 DCHECK(GetInstance() != NULL); | |
| 331 GetInstance()->UploadData(); | |
| 332 if (GetInstance()->initial_uma_upload_) { | |
| 333 // If this is the first uma upload by this process then subsequent uma | |
| 334 // uploads should occur once every 10 minutes(default). | |
| 335 GetInstance()->initial_uma_upload_ = false; | |
| 336 DCHECK(GetInstance()->transmission_timer_id_ != 0); | |
| 337 SetTimer(NULL, GetInstance()->transmission_timer_id_, | |
| 338 kMinMilliSecondsPerUMAUpload, | |
| 339 reinterpret_cast<TIMERPROC>(TransmissionTimerProc)); | |
| 340 } | |
| 341 } | |
| 342 | |
| 343 void MetricsService::SetReporting(bool enable) { | |
| 344 static const int kChromeFrameMetricsTimerId = 0xFFFFFFFF; | |
| 345 | |
| 346 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 347 if (reporting_active_ != enable) { | |
| 348 reporting_active_ = enable; | |
| 349 if (reporting_active_) { | |
| 350 transmission_timer_id_ = | |
| 351 SetTimer(NULL, kChromeFrameMetricsTimerId, | |
| 352 kInitialUMAUploadTimeoutMilliSeconds, | |
| 353 reinterpret_cast<TIMERPROC>(TransmissionTimerProc)); | |
| 354 } else { | |
| 355 UploadData(); | |
| 356 } | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 //------------------------------------------------------------------------------ | |
| 361 // Recording control methods | |
| 362 | |
| 363 void MetricsService::StartRecording() { | |
| 364 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 365 if (log_manager_.current_log()) | |
| 366 return; | |
| 367 | |
| 368 MetricsLogBase::LogType log_type = (state_ == INITIALIZED) ? | |
| 369 MetricsLogBase::INITIAL_LOG : MetricsLogBase::ONGOING_LOG; | |
| 370 log_manager_.BeginLoggingWithLog(new MetricsLogBase(GetClientID(), | |
| 371 session_id_, | |
| 372 GetVersionString()), | |
| 373 log_type); | |
| 374 if (state_ == INITIALIZED) | |
| 375 state_ = ACTIVE; | |
| 376 } | |
| 377 | |
| 378 void MetricsService::StopRecording(bool save_log) { | |
| 379 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 380 if (!log_manager_.current_log()) | |
| 381 return; | |
| 382 | |
| 383 // Put incremental histogram deltas at the end of all log transmissions. | |
| 384 // Don't bother if we're going to discard current_log. | |
| 385 if (save_log) { | |
| 386 CrashMetricsReporter::GetInstance()->RecordCrashMetrics(); | |
| 387 RecordCurrentHistograms(); | |
| 388 } | |
| 389 | |
| 390 if (save_log) { | |
| 391 log_manager_.FinishCurrentLog(); | |
| 392 log_manager_.StageNextLogForUpload(); | |
| 393 } else { | |
| 394 log_manager_.DiscardCurrentLog(); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 void MetricsService::MakePendingLog() { | |
| 399 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 400 if (log_manager_.has_staged_log()) | |
| 401 return; | |
| 402 | |
| 403 if (state_ != ACTIVE) { | |
| 404 NOTREACHED(); | |
| 405 return; | |
| 406 } | |
| 407 | |
| 408 StopRecording(true); | |
| 409 StartRecording(); | |
| 410 } | |
| 411 | |
| 412 bool MetricsService::TransmissionPermitted() const { | |
| 413 // If the user forbids uploading that's their business, and we don't upload | |
| 414 // anything. | |
| 415 return user_permits_upload_; | |
| 416 } | |
| 417 | |
| 418 bool MetricsService::UploadData() { | |
| 419 DCHECK_EQ(thread_, base::PlatformThread::CurrentId()); | |
| 420 | |
| 421 if (!GetInstance()->TransmissionPermitted()) | |
| 422 return false; | |
| 423 | |
| 424 static long currently_uploading = 0; | |
| 425 if (InterlockedCompareExchange(¤tly_uploading, 1, 0)) { | |
| 426 DVLOG(1) << "Contention for uploading metrics data. Backing off"; | |
| 427 return false; | |
| 428 } | |
| 429 | |
| 430 MakePendingLog(); | |
| 431 | |
| 432 bool ret = true; | |
| 433 | |
| 434 if (log_manager_.has_staged_log()) { | |
| 435 HRESULT hr = ChromeFrameMetricsDataUploader::UploadDataHelper( | |
| 436 log_manager_.staged_log_text(), kServerUrl, kMimeType); | |
| 437 DCHECK(SUCCEEDED(hr)); | |
| 438 log_manager_.DiscardStagedLog(); | |
| 439 } else { | |
| 440 NOTREACHED(); | |
| 441 ret = false; | |
| 442 } | |
| 443 | |
| 444 currently_uploading = 0; | |
| 445 return ret; | |
| 446 } | |
| 447 | |
| 448 // static | |
| 449 std::string MetricsService::GetVersionString() { | |
| 450 chrome::VersionInfo version_info; | |
| 451 if (version_info.is_valid()) { | |
| 452 std::string version = version_info.Version(); | |
| 453 // Add the -F extensions to ensure that UMA data uploaded by ChromeFrame | |
| 454 // lands in the ChromeFrame bucket. | |
| 455 version += "-F"; | |
| 456 if (!version_info.IsOfficialBuild()) | |
| 457 version.append("-devel"); | |
| 458 return version; | |
| 459 } else { | |
| 460 NOTREACHED() << "Unable to retrieve version string."; | |
| 461 } | |
| 462 | |
| 463 return std::string(); | |
| 464 } | |
| OLD | NEW |