blob: 73c93b2eacdaf3c9c3c5582c5df2ccdebd924f7c [file] [log] [blame]
[email protected]2e4cd1a2012-01-12 08:51:031// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]897b26272010-06-11 02:23:442// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]897b26272010-06-11 02:23:445//------------------------------------------------------------------------------
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 XML. Transmission includes submitting a compressed log
16// as data in a URL-get, and is performed using functionality provided by
17// 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
[email protected]180fa672010-09-03 21:11:4642#include <atlbase.h>
43#include <atlwin.h>
[email protected]897b26272010-06-11 02:23:4444#include <objbase.h>
[email protected]ac7f3fdb2010-11-12 12:47:0545#include <windows.h>
46
[email protected]897b26272010-06-11 02:23:4447#if defined(USE_SYSTEM_LIBBZ2)
48#include <bzlib.h>
49#else
50#include "third_party/bzip2/bzlib.h"
51#endif
52
[email protected]567d30e2012-07-13 21:48:2953#include "base/metrics/statistics_recorder.h"
[email protected]add184df2012-03-10 10:08:5754#include "base/string16.h"
[email protected]897b26272010-06-11 02:23:4455#include "base/string_util.h"
[email protected]d3451d832010-10-01 11:17:3756#include "base/stringprintf.h"
[email protected]20305ec2011-01-21 04:55:5257#include "base/synchronization/lock.h"
[email protected]528c56d2010-07-30 19:28:4458#include "base/string_number_conversions.h"
[email protected]897b26272010-06-11 02:23:4459#include "base/utf_string_conversions.h"
[email protected]13a8095f2011-04-20 18:17:0460#include "base/win/scoped_comptr.h"
[email protected]1eeb5e02010-07-20 23:02:1161#include "chrome/common/chrome_version_info.h"
[email protected]e182be02012-01-27 02:35:4462#include "chrome/common/metrics/metrics_log_base.h"
[email protected]2e4cd1a2012-01-12 08:51:0363#include "chrome/common/metrics/metrics_log_manager.h"
[email protected]897b26272010-06-11 02:23:4464#include "chrome/installer/util/browser_distribution.h"
[email protected]897b26272010-06-11 02:23:4465#include "chrome/installer/util/google_update_settings.h"
66#include "chrome_frame/bind_status_callback_impl.h"
[email protected]9607f6a82010-06-12 17:24:4167#include "chrome_frame/crash_reporting/crash_metrics.h"
[email protected]e0a26282010-08-27 23:18:1068#include "chrome_frame/html_utils.h"
[email protected]897b26272010-06-11 02:23:4469#include "chrome_frame/utils.h"
[email protected]897b26272010-06-11 02:23:4470
71using base::Time;
72using base::TimeDelta;
[email protected]13a8095f2011-04-20 18:17:0473using base::win::ScopedComPtr;
[email protected]897b26272010-06-11 02:23:4474
[email protected]f985b9c2010-06-18 17:05:1075// The first UMA upload occurs after this interval.
76static const int kInitialUMAUploadTimeoutMilliSeconds = 30000;
[email protected]897b26272010-06-11 02:23:4477
[email protected]f985b9c2010-06-18 17:05:1078// Default to one UMA upload per 10 mins.
79static const int kMinMilliSecondsPerUMAUpload = 600000;
[email protected]897b26272010-06-11 02:23:4480
[email protected]13a8095f2011-04-20 18:17:0481base::LazyInstance<base::ThreadLocalPointer<MetricsService> >
[email protected]6de0fd1d2011-11-15 13:31:4982 MetricsService::g_metrics_instance_ = LAZY_INSTANCE_INITIALIZER;
[email protected]606e5bf2010-09-16 00:41:1983
[email protected]add184df2012-03-10 10:08:5784std::string MetricsService::client_id_;
85
[email protected]20305ec2011-01-21 04:55:5286base::Lock MetricsService::metrics_service_lock_;
[email protected]21a45aa2010-12-04 01:00:1887
[email protected]897b26272010-06-11 02:23:4488// 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.
[email protected]13a8095f2011-04-20 18:17:0491class ChromeFrameMetricsDataUploader : public BSCBImpl {
[email protected]897b26272010-06-11 02:23:4492 public:
93 ChromeFrameMetricsDataUploader()
[email protected]13a8095f2011-04-20 18:17:0494 : cache_stream_(NULL),
95 upload_data_size_(0) {
[email protected]2b9a9f162010-10-19 20:30:4596 DVLOG(1) << __FUNCTION__;
[email protected]897b26272010-06-11 02:23:4497 }
98
99 ~ChromeFrameMetricsDataUploader() {
[email protected]2b9a9f162010-10-19 20:30:45100 DVLOG(1) << __FUNCTION__;
[email protected]897b26272010-06-11 02:23:44101 }
102
103 static HRESULT ChromeFrameMetricsDataUploader::UploadDataHelper(
[email protected]448ca4d2012-05-09 19:53:12104 const std::string& upload_data,
105 const std::string& server_url,
106 const std::string& mime_type) {
[email protected]13a8095f2011-04-20 18:17:04107 CComObject<ChromeFrameMetricsDataUploader>* data_uploader = NULL;
108 CComObject<ChromeFrameMetricsDataUploader>::CreateInstance(&data_uploader);
109 DCHECK(data_uploader != NULL);
[email protected]897b26272010-06-11 02:23:44110
[email protected]13a8095f2011-04-20 18:17:04111 data_uploader->AddRef();
[email protected]448ca4d2012-05-09 19:53:12112 HRESULT hr = data_uploader->UploadData(upload_data, server_url, mime_type);
[email protected]13a8095f2011-04-20 18:17:04113 if (FAILED(hr)) {
114 DLOG(ERROR) << "Failed to initialize ChromeFrame UMA data uploader: Err"
115 << hr;
[email protected]606e5bf2010-09-16 00:41:19116 }
[email protected]13a8095f2011-04-20 18:17:04117 data_uploader->Release();
118 return hr;
[email protected]897b26272010-06-11 02:23:44119 }
120
[email protected]448ca4d2012-05-09 19:53:12121 HRESULT UploadData(const std::string& upload_data,
122 const std::string& server_url,
123 const std::string& mime_type) {
[email protected]13a8095f2011-04-20 18:17:04124 if (upload_data.empty()) {
125 NOTREACHED() << "Invalid upload data";
126 return E_INVALIDARG;
127 }
[email protected]897b26272010-06-11 02:23:44128
[email protected]13a8095f2011-04-20 18:17:04129 DCHECK(cache_stream_.get() == NULL);
[email protected]897b26272010-06-11 02:23:44130
[email protected]13a8095f2011-04-20 18:17:04131 upload_data_size_ = upload_data.size() + 1;
[email protected]897b26272010-06-11 02:23:44132
[email protected]13a8095f2011-04-20 18:17:04133 HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, cache_stream_.Receive());
134 if (FAILED(hr)) {
135 NOTREACHED() << "Failed to create stream. Error:"
136 << hr;
137 return hr;
138 }
139
140 DCHECK(cache_stream_.get());
141
142 unsigned long written = 0;
143 cache_stream_->Write(upload_data.c_str(), upload_data_size_, &written);
144 DCHECK(written == upload_data_size_);
145
146 RewindStream(cache_stream_);
147
[email protected]448ca4d2012-05-09 19:53:12148 server_url_ = ASCIIToWide(server_url);
149 mime_type_ = mime_type;
[email protected]13a8095f2011-04-20 18:17:04150 DCHECK(!server_url_.empty());
[email protected]448ca4d2012-05-09 19:53:12151 DCHECK(!mime_type_.empty());
[email protected]13a8095f2011-04-20 18:17:04152
153 hr = CreateURLMoniker(NULL, server_url_.c_str(),
154 upload_moniker_.Receive());
155 if (FAILED(hr)) {
156 DLOG(ERROR) << "Failed to create url moniker for url:"
157 << server_url_.c_str()
158 << " Error:"
159 << hr;
160 } else {
161 ScopedComPtr<IBindCtx> context;
162 hr = CreateAsyncBindCtx(0, this, NULL, context.Receive());
163 DCHECK(SUCCEEDED(hr));
164 DCHECK(context);
165
166 ScopedComPtr<IStream> stream;
167 hr = upload_moniker_->BindToStorage(
168 context, NULL, IID_IStream,
169 reinterpret_cast<void**>(stream.Receive()));
170 if (FAILED(hr)) {
171 NOTREACHED();
172 DLOG(ERROR) << "Failed to bind to upload data moniker. Error:"
173 << hr;
174 }
175 }
176 return hr;
[email protected]897b26272010-06-11 02:23:44177 }
178
[email protected]13a8095f2011-04-20 18:17:04179 STDMETHOD(BeginningTransaction)(LPCWSTR url, LPCWSTR headers, DWORD reserved,
180 LPWSTR* additional_headers) {
181 std::string new_headers;
182 new_headers =
183 StringPrintf("Content-Length: %s\r\n"
184 "Content-Type: %s\r\n"
185 "%s\r\n",
186 base::Int64ToString(upload_data_size_).c_str(),
[email protected]448ca4d2012-05-09 19:53:12187 mime_type_.c_str(),
[email protected]5121e6782011-05-19 21:53:19188 http_utils::GetDefaultUserAgentHeaderWithCFTag().c_str());
[email protected]897b26272010-06-11 02:23:44189
[email protected]13a8095f2011-04-20 18:17:04190 *additional_headers = reinterpret_cast<wchar_t*>(
191 CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t)));
192
193 lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(),
194 new_headers.size());
195
196 return BSCBImpl::BeginningTransaction(url, headers, reserved,
197 additional_headers);
198 }
199
200 STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) {
201 if ((bind_info == NULL) || (bind_info->cbSize == 0) ||
202 (bind_flags == NULL))
203 return E_INVALIDARG;
204
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;
209
210 DCHECK(cache_stream_.get());
211
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);
221 }
222
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);
[email protected]897b26272010-06-11 02:23:44228 }
229
230 private:
[email protected]13a8095f2011-04-20 18:17:04231 std::wstring server_url_;
[email protected]448ca4d2012-05-09 19:53:12232 std::string mime_type_;
[email protected]13a8095f2011-04-20 18:17:04233 size_t upload_data_size_;
234 ScopedComPtr<IStream> cache_stream_;
235 ScopedComPtr<IMoniker> upload_moniker_;
[email protected]897b26272010-06-11 02:23:44236};
237
238MetricsService* MetricsService::GetInstance() {
[email protected]13a8095f2011-04-20 18:17:04239 if (g_metrics_instance_.Pointer()->Get())
240 return g_metrics_instance_.Pointer()->Get();
241
242 g_metrics_instance_.Pointer()->Set(new MetricsService);
243 return g_metrics_instance_.Pointer()->Get();
[email protected]897b26272010-06-11 02:23:44244}
245
246MetricsService::MetricsService()
247 : recording_active_(false),
248 reporting_active_(false),
249 user_permits_upload_(false),
250 state_(INITIALIZED),
[email protected]f985b9c2010-06-18 17:05:10251 thread_(NULL),
252 initial_uma_upload_(true),
253 transmission_timer_id_(0) {
[email protected]897b26272010-06-11 02:23:44254}
255
256MetricsService::~MetricsService() {
257 SetRecording(false);
[email protected]897b26272010-06-11 02:23:44258}
259
260void MetricsService::InitializeMetricsState() {
261 DCHECK(state_ == INITIALIZED);
262
[email protected]f214f8792011-01-01 02:17:08263 thread_ = base::PlatformThread::CurrentId();
[email protected]897b26272010-06-11 02:23:44264
265 user_permits_upload_ = GoogleUpdateSettings::GetCollectStatsConsent();
266 // Update session ID
267 session_id_ = CrashMetricsReporter::GetInstance()->IncrementMetric(
268 CrashMetricsReporter::SESSION_ID);
269
[email protected]fce44c12012-07-19 19:17:32270 base::StatisticsRecorder::Initialize();
[email protected]897b26272010-06-11 02:23:44271 CrashMetricsReporter::GetInstance()->set_active(true);
272}
273
274// static
275void MetricsService::Start() {
[email protected]20305ec2011-01-21 04:55:52276 base::AutoLock lock(metrics_service_lock_);
[email protected]21a45aa2010-12-04 01:00:18277
[email protected]897b26272010-06-11 02:23:44278 if (GetInstance()->state_ == ACTIVE)
279 return;
280
281 GetInstance()->InitializeMetricsState();
282 GetInstance()->SetRecording(true);
283 GetInstance()->SetReporting(true);
284}
285
286// static
287void MetricsService::Stop() {
[email protected]13a8095f2011-04-20 18:17:04288 base::AutoLock lock(metrics_service_lock_);
[email protected]21a45aa2010-12-04 01:00:18289
[email protected]13a8095f2011-04-20 18:17:04290 GetInstance()->SetReporting(false);
291 GetInstance()->SetRecording(false);
[email protected]897b26272010-06-11 02:23:44292}
293
294void MetricsService::SetRecording(bool enabled) {
[email protected]13a8095f2011-04-20 18:17:04295 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]897b26272010-06-11 02:23:44296 if (enabled == recording_active_)
297 return;
298
299 if (enabled) {
[email protected]897b26272010-06-11 02:23:44300 StartRecording();
[email protected]897b26272010-06-11 02:23:44301 } else {
302 state_ = STOPPED;
303 }
304 recording_active_ = enabled;
305}
306
307// static
[email protected]add184df2012-03-10 10:08:57308const 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;
[email protected]897b26272010-06-11 02:23:44314
[email protected]add184df2012-03-10 10:08:57315 GUID guid;
316 HRESULT guid_result = CoCreateGuid(&guid);
317 DCHECK(SUCCEEDED(guid_result));
[email protected]897b26272010-06-11 02:23:44318
[email protected]add184df2012-03-10 10:08:57319 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));
324 }
325 return client_id_;
[email protected]897b26272010-06-11 02:23:44326}
327
328// static
329void CALLBACK MetricsService::TransmissionTimerProc(HWND window,
330 unsigned int message,
331 unsigned int event_id,
332 unsigned int time) {
[email protected]2b9a9f162010-10-19 20:30:45333 DVLOG(1) << "Transmission timer notified";
[email protected]897b26272010-06-11 02:23:44334 DCHECK(GetInstance() != NULL);
335 GetInstance()->UploadData();
[email protected]f985b9c2010-06-18 17:05:10336 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));
344 }
[email protected]897b26272010-06-11 02:23:44345}
346
347void MetricsService::SetReporting(bool enable) {
348 static const int kChromeFrameMetricsTimerId = 0xFFFFFFFF;
349
[email protected]f214f8792011-01-01 02:17:08350 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]897b26272010-06-11 02:23:44351 if (reporting_active_ != enable) {
352 reporting_active_ = enable;
353 if (reporting_active_) {
[email protected]f985b9c2010-06-18 17:05:10354 transmission_timer_id_ =
355 SetTimer(NULL, kChromeFrameMetricsTimerId,
356 kInitialUMAUploadTimeoutMilliSeconds,
357 reinterpret_cast<TIMERPROC>(TransmissionTimerProc));
[email protected]23f919a2010-11-22 03:20:59358 } else {
359 UploadData();
[email protected]897b26272010-06-11 02:23:44360 }
361 }
362}
363
364//------------------------------------------------------------------------------
365// Recording control methods
366
367void MetricsService::StartRecording() {
[email protected]f214f8792011-01-01 02:17:08368 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]cac267c2011-09-29 15:18:10369 if (log_manager_.current_log())
[email protected]897b26272010-06-11 02:23:44370 return;
371
[email protected]29948262012-03-01 12:15:08372 MetricsLogManager::LogType log_type = (state_ == INITIALIZED) ?
373 MetricsLogManager::INITIAL_LOG : MetricsLogManager::ONGOING_LOG;
[email protected]add184df2012-03-10 10:08:57374 log_manager_.BeginLoggingWithLog(new MetricsLogBase(GetClientID(),
375 session_id_,
[email protected]29948262012-03-01 12:15:08376 GetVersionString()),
377 log_type);
[email protected]897b26272010-06-11 02:23:44378 if (state_ == INITIALIZED)
379 state_ = ACTIVE;
380}
381
382void MetricsService::StopRecording(bool save_log) {
[email protected]f214f8792011-01-01 02:17:08383 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]cac267c2011-09-29 15:18:10384 if (!log_manager_.current_log())
[email protected]897b26272010-06-11 02:23:44385 return;
386
387 // Put incremental histogram deltas at the end of all log transmissions.
[email protected]cac267c2011-09-29 15:18:10388 // Don't bother if we're going to discard current_log.
[email protected]897b26272010-06-11 02:23:44389 if (save_log) {
390 CrashMetricsReporter::GetInstance()->RecordCrashMetrics();
391 RecordCurrentHistograms();
392 }
393
[email protected]29948262012-03-01 12:15:08394 if (save_log) {
395 log_manager_.FinishCurrentLog();
396 log_manager_.StageNextLogForUpload();
397 } else {
[email protected]cac267c2011-09-29 15:18:10398 log_manager_.DiscardCurrentLog();
[email protected]29948262012-03-01 12:15:08399 }
[email protected]897b26272010-06-11 02:23:44400}
401
402void MetricsService::MakePendingLog() {
[email protected]f214f8792011-01-01 02:17:08403 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]cac267c2011-09-29 15:18:10404 if (log_manager_.has_staged_log())
[email protected]897b26272010-06-11 02:23:44405 return;
406
[email protected]448ca4d2012-05-09 19:53:12407 if (state_ != ACTIVE) {
408 NOTREACHED();
409 return;
[email protected]897b26272010-06-11 02:23:44410 }
[email protected]448ca4d2012-05-09 19:53:12411
412 StopRecording(true);
413 StartRecording();
[email protected]897b26272010-06-11 02:23:44414}
415
416bool MetricsService::TransmissionPermitted() const {
417 // If the user forbids uploading that's their business, and we don't upload
418 // anything.
419 return user_permits_upload_;
420}
421
[email protected]897b26272010-06-11 02:23:44422bool MetricsService::UploadData() {
[email protected]f214f8792011-01-01 02:17:08423 DCHECK_EQ(thread_, base::PlatformThread::CurrentId());
[email protected]897b26272010-06-11 02:23:44424
425 if (!GetInstance()->TransmissionPermitted())
426 return false;
427
428 static long currently_uploading = 0;
429 if (InterlockedCompareExchange(&currently_uploading, 1, 0)) {
[email protected]2b9a9f162010-10-19 20:30:45430 DVLOG(1) << "Contention for uploading metrics data. Backing off";
[email protected]897b26272010-06-11 02:23:44431 return false;
432 }
433
[email protected]cac267c2011-09-29 15:18:10434 MakePendingLog();
[email protected]897b26272010-06-11 02:23:44435
436 bool ret = true;
437
[email protected]29948262012-03-01 12:15:08438 if (log_manager_.has_staged_log()) {
[email protected]897b26272010-06-11 02:23:44439 HRESULT hr = ChromeFrameMetricsDataUploader::UploadDataHelper(
[email protected]448ca4d2012-05-09 19:53:12440 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);
[email protected]897b26272010-06-11 02:23:44444 DCHECK(SUCCEEDED(hr));
[email protected]29948262012-03-01 12:15:08445 log_manager_.DiscardStagedLog();
446 } else {
447 NOTREACHED();
448 ret = false;
[email protected]897b26272010-06-11 02:23:44449 }
[email protected]897b26272010-06-11 02:23:44450
451 currently_uploading = 0;
452 return ret;
453}
454
455// static
456std::string MetricsService::GetVersionString() {
[email protected]0211f57e2010-08-27 20:28:42457 chrome::VersionInfo version_info;
[email protected]30c91802010-12-18 00:40:17458 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.
462 version += "-F";
463 if (!version_info.IsOfficialBuild())
464 version.append("-devel");
465 return version;
466 } else {
467 NOTREACHED() << "Unable to retrieve version string.";
468 }
469
470 return std::string();
[email protected]897b26272010-06-11 02:23:44471}