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.
5 //------------------------------------------------------------------------------
6 // Description of the life cycle of a instance of MetricsService.
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 XML. Transmission includes submitting a compressed log
16 // as data in a URL-get, and is performed using functionality provided by
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.
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.
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.
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
34 // INITIALIZED, // Constructor was called.
35 // ACTIVE, // Accumalating log data.
36 // STOPPED, // Service has stopped.
38 //-----------------------------------------------------------------------------
40 #include "chrome_frame/metrics_service.h"
47 #if defined(USE_SYSTEM_LIBBZ2)
50 #include "third_party/bzip2/bzlib.h"
53 #include "base/metrics/statistics_recorder.h"
54 #include "base/string16.h"
55 #include "base/string_util.h"
56 #include "base/stringprintf.h"
57 #include "base/synchronization/lock.h"
58 #include "base/string_number_conversions.h"
59 #include "base/utf_string_conversions.h"
60 #include "base/win/scoped_comptr.h"
61 #include "chrome/common/chrome_version_info.h"
62 #include "chrome/common/metrics/metrics_log_base.h"
63 #include "chrome/common/metrics/metrics_log_manager.h"
64 #include "chrome/installer/util/browser_distribution.h"
65 #include "chrome/installer/util/google_update_settings.h"
66 #include "chrome_frame/bind_status_callback_impl.h"
67 #include "chrome_frame/crash_reporting/crash_metrics.h"
68 #include "chrome_frame/html_utils.h"
69 #include "chrome_frame/utils.h"
72 using base::TimeDelta
;
73 using base::win::ScopedComPtr
;
75 // The first UMA upload occurs after this interval.
76 static const int kInitialUMAUploadTimeoutMilliSeconds
= 30000;
78 // Default to one UMA upload per 10 mins.
79 static const int kMinMilliSecondsPerUMAUpload
= 600000;
81 base::LazyInstance
<base::ThreadLocalPointer
<MetricsService
> >
82 MetricsService::g_metrics_instance_
= LAZY_INSTANCE_INITIALIZER
;
84 std::string
MetricsService::client_id_
;
86 base::Lock
MetricsService::metrics_service_lock_
;
88 // This class provides functionality to upload the ChromeFrame UMA data to the
89 // server. An instance of this class is created whenever we have data to be
90 // uploaded to the server.
91 class ChromeFrameMetricsDataUploader
: public BSCBImpl
{
93 ChromeFrameMetricsDataUploader()
94 : cache_stream_(NULL
),
95 upload_data_size_(0) {
96 DVLOG(1) << __FUNCTION__
;
99 ~ChromeFrameMetricsDataUploader() {
100 DVLOG(1) << __FUNCTION__
;
103 static HRESULT
ChromeFrameMetricsDataUploader::UploadDataHelper(
104 const std::string
& upload_data
,
105 const std::string
& server_url
,
106 const std::string
& mime_type
) {
107 CComObject
<ChromeFrameMetricsDataUploader
>* data_uploader
= NULL
;
108 CComObject
<ChromeFrameMetricsDataUploader
>::CreateInstance(&data_uploader
);
109 DCHECK(data_uploader
!= NULL
);
111 data_uploader
->AddRef();
112 HRESULT hr
= data_uploader
->UploadData(upload_data
, server_url
, mime_type
);
114 DLOG(ERROR
) << "Failed to initialize ChromeFrame UMA data uploader: Err"
117 data_uploader
->Release();
121 HRESULT
UploadData(const std::string
& upload_data
,
122 const std::string
& server_url
,
123 const std::string
& mime_type
) {
124 if (upload_data
.empty()) {
125 NOTREACHED() << "Invalid upload data";
129 DCHECK(cache_stream_
.get() == NULL
);
131 upload_data_size_
= upload_data
.size() + 1;
133 HRESULT hr
= CreateStreamOnHGlobal(NULL
, TRUE
, cache_stream_
.Receive());
135 NOTREACHED() << "Failed to create stream. Error:"
140 DCHECK(cache_stream_
.get());
142 unsigned long written
= 0;
143 cache_stream_
->Write(upload_data
.c_str(), upload_data_size_
, &written
);
144 DCHECK(written
== upload_data_size_
);
146 RewindStream(cache_stream_
);
148 server_url_
= ASCIIToWide(server_url
);
149 mime_type_
= mime_type
;
150 DCHECK(!server_url_
.empty());
151 DCHECK(!mime_type_
.empty());
153 hr
= CreateURLMoniker(NULL
, server_url_
.c_str(),
154 upload_moniker_
.Receive());
156 DLOG(ERROR
) << "Failed to create url moniker for url:"
157 << server_url_
.c_str()
161 ScopedComPtr
<IBindCtx
> context
;
162 hr
= CreateAsyncBindCtx(0, this, NULL
, context
.Receive());
163 DCHECK(SUCCEEDED(hr
));
166 ScopedComPtr
<IStream
> stream
;
167 hr
= upload_moniker_
->BindToStorage(
168 context
, NULL
, IID_IStream
,
169 reinterpret_cast<void**>(stream
.Receive()));
172 DLOG(ERROR
) << "Failed to bind to upload data moniker. Error:"
179 STDMETHOD(BeginningTransaction
)(LPCWSTR url
, LPCWSTR headers
, DWORD reserved
,
180 LPWSTR
* additional_headers
) {
181 std::string new_headers
;
183 StringPrintf("Content-Length: %s\r\n"
184 "Content-Type: %s\r\n"
186 base::Int64ToString(upload_data_size_
).c_str(),
188 http_utils::GetDefaultUserAgentHeaderWithCFTag().c_str());
190 *additional_headers
= reinterpret_cast<wchar_t*>(
191 CoTaskMemAlloc((new_headers
.size() + 1) * sizeof(wchar_t)));
193 lstrcpynW(*additional_headers
, ASCIIToWide(new_headers
).c_str(),
196 return BSCBImpl::BeginningTransaction(url
, headers
, reserved
,
200 STDMETHOD(GetBindInfo
)(DWORD
* bind_flags
, BINDINFO
* bind_info
) {
201 if ((bind_info
== NULL
) || (bind_info
->cbSize
== 0) ||
202 (bind_flags
== NULL
))
205 *bind_flags
= BINDF_ASYNCHRONOUS
| BINDF_ASYNCSTORAGE
| BINDF_PULLDATA
;
206 // Bypass caching proxies on POSTs and PUTs and avoid writing responses to
207 // these requests to the browser's cache
208 *bind_flags
|= BINDF_GETNEWESTVERSION
| BINDF_PRAGMA_NO_CACHE
;
210 DCHECK(cache_stream_
.get());
212 // Initialize the STGMEDIUM.
213 memset(&bind_info
->stgmedData
, 0, sizeof(STGMEDIUM
));
214 bind_info
->grfBindInfoF
= 0;
215 bind_info
->szCustomVerb
= NULL
;
216 bind_info
->dwBindVerb
= BINDVERB_POST
;
217 bind_info
->stgmedData
.tymed
= TYMED_ISTREAM
;
218 bind_info
->stgmedData
.pstm
= cache_stream_
.get();
219 bind_info
->stgmedData
.pstm
->AddRef();
220 return BSCBImpl::GetBindInfo(bind_flags
, bind_info
);
223 STDMETHOD(OnResponse
)(DWORD response_code
, LPCWSTR response_headers
,
224 LPCWSTR request_headers
, LPWSTR
* additional_headers
) {
225 DVLOG(1) << __FUNCTION__
<< " headers: \n" << response_headers
;
226 return BSCBImpl::OnResponse(response_code
, response_headers
,
227 request_headers
, additional_headers
);
231 std::wstring server_url_
;
232 std::string mime_type_
;
233 size_t upload_data_size_
;
234 ScopedComPtr
<IStream
> cache_stream_
;
235 ScopedComPtr
<IMoniker
> upload_moniker_
;
238 MetricsService
* MetricsService::GetInstance() {
239 if (g_metrics_instance_
.Pointer()->Get())
240 return g_metrics_instance_
.Pointer()->Get();
242 g_metrics_instance_
.Pointer()->Set(new MetricsService
);
243 return g_metrics_instance_
.Pointer()->Get();
246 MetricsService::MetricsService()
247 : recording_active_(false),
248 reporting_active_(false),
249 user_permits_upload_(false),
252 initial_uma_upload_(true),
253 transmission_timer_id_(0) {
256 MetricsService::~MetricsService() {
260 void MetricsService::InitializeMetricsState() {
261 DCHECK(state_
== INITIALIZED
);
263 thread_
= base::PlatformThread::CurrentId();
265 user_permits_upload_
= GoogleUpdateSettings::GetCollectStatsConsent();
267 session_id_
= CrashMetricsReporter::GetInstance()->IncrementMetric(
268 CrashMetricsReporter::SESSION_ID
);
270 base::StatisticsRecorder::Initialize();
271 CrashMetricsReporter::GetInstance()->set_active(true);
275 void MetricsService::Start() {
276 base::AutoLock
lock(metrics_service_lock_
);
278 if (GetInstance()->state_
== ACTIVE
)
281 GetInstance()->InitializeMetricsState();
282 GetInstance()->SetRecording(true);
283 GetInstance()->SetReporting(true);
287 void MetricsService::Stop() {
288 base::AutoLock
lock(metrics_service_lock_
);
290 GetInstance()->SetReporting(false);
291 GetInstance()->SetRecording(false);
294 void MetricsService::SetRecording(bool enabled
) {
295 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
296 if (enabled
== recording_active_
)
304 recording_active_
= enabled
;
308 const std::string
& MetricsService::GetClientID() {
309 // TODO(robertshield): Chrome Frame shouldn't generate a new ID on every run
310 // as this apparently breaks some assumptions during metric analysis.
311 // See http://crbug.com/117188
312 if (client_id_
.empty()) {
313 const int kGUIDSize
= 39;
316 HRESULT guid_result
= CoCreateGuid(&guid
);
317 DCHECK(SUCCEEDED(guid_result
));
319 string16 guid_string
;
320 int result
= StringFromGUID2(guid
,
321 WriteInto(&guid_string
, kGUIDSize
), kGUIDSize
);
322 DCHECK(result
== kGUIDSize
);
323 client_id_
= WideToUTF8(guid_string
.substr(1, guid_string
.length() - 2));
329 void CALLBACK
MetricsService::TransmissionTimerProc(HWND window
,
330 unsigned int message
,
331 unsigned int event_id
,
333 DVLOG(1) << "Transmission timer notified";
334 DCHECK(GetInstance() != NULL
);
335 GetInstance()->UploadData();
336 if (GetInstance()->initial_uma_upload_
) {
337 // If this is the first uma upload by this process then subsequent uma
338 // uploads should occur once every 10 minutes(default).
339 GetInstance()->initial_uma_upload_
= false;
340 DCHECK(GetInstance()->transmission_timer_id_
!= 0);
341 SetTimer(NULL
, GetInstance()->transmission_timer_id_
,
342 kMinMilliSecondsPerUMAUpload
,
343 reinterpret_cast<TIMERPROC
>(TransmissionTimerProc
));
347 void MetricsService::SetReporting(bool enable
) {
348 static const int kChromeFrameMetricsTimerId
= 0xFFFFFFFF;
350 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
351 if (reporting_active_
!= enable
) {
352 reporting_active_
= enable
;
353 if (reporting_active_
) {
354 transmission_timer_id_
=
355 SetTimer(NULL
, kChromeFrameMetricsTimerId
,
356 kInitialUMAUploadTimeoutMilliSeconds
,
357 reinterpret_cast<TIMERPROC
>(TransmissionTimerProc
));
364 //------------------------------------------------------------------------------
365 // Recording control methods
367 void MetricsService::StartRecording() {
368 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
369 if (log_manager_
.current_log())
372 MetricsLogManager::LogType log_type
= (state_
== INITIALIZED
) ?
373 MetricsLogManager::INITIAL_LOG
: MetricsLogManager::ONGOING_LOG
;
374 log_manager_
.BeginLoggingWithLog(new MetricsLogBase(GetClientID(),
378 if (state_
== INITIALIZED
)
382 void MetricsService::StopRecording(bool save_log
) {
383 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
384 if (!log_manager_
.current_log())
387 // Put incremental histogram deltas at the end of all log transmissions.
388 // Don't bother if we're going to discard current_log.
390 CrashMetricsReporter::GetInstance()->RecordCrashMetrics();
391 RecordCurrentHistograms();
395 log_manager_
.FinishCurrentLog();
396 log_manager_
.StageNextLogForUpload();
398 log_manager_
.DiscardCurrentLog();
402 void MetricsService::MakePendingLog() {
403 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
404 if (log_manager_
.has_staged_log())
407 if (state_
!= ACTIVE
) {
416 bool MetricsService::TransmissionPermitted() const {
417 // If the user forbids uploading that's their business, and we don't upload
419 return user_permits_upload_
;
422 bool MetricsService::UploadData() {
423 DCHECK_EQ(thread_
, base::PlatformThread::CurrentId());
425 if (!GetInstance()->TransmissionPermitted())
428 static long currently_uploading
= 0;
429 if (InterlockedCompareExchange(¤tly_uploading
, 1, 0)) {
430 DVLOG(1) << "Contention for uploading metrics data. Backing off";
438 if (log_manager_
.has_staged_log()) {
439 HRESULT hr
= ChromeFrameMetricsDataUploader::UploadDataHelper(
440 log_manager_
.staged_log_text().xml
, kServerUrlXml
, kMimeTypeXml
);
441 DCHECK(SUCCEEDED(hr
));
442 hr
= ChromeFrameMetricsDataUploader::UploadDataHelper(
443 log_manager_
.staged_log_text().proto
, kServerUrlProto
, kMimeTypeProto
);
444 DCHECK(SUCCEEDED(hr
));
445 log_manager_
.DiscardStagedLog();
451 currently_uploading
= 0;
456 std::string
MetricsService::GetVersionString() {
457 chrome::VersionInfo version_info
;
458 if (version_info
.is_valid()) {
459 std::string version
= version_info
.Version();
460 // Add the -F extensions to ensure that UMA data uploaded by ChromeFrame
461 // lands in the ChromeFrame bucket.
463 if (!version_info
.IsOfficialBuild())
464 version
.append("-devel");
467 NOTREACHED() << "Unable to retrieve version string.";
470 return std::string();