aw_metrics_service_client.cc 10.6 KB
Newer Older
1
// Copyright 2017 The Chromium Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "android_webview/browser/aw_metrics_service_client.h"

7
#include "android_webview/browser/aw_metrics_log_uploader.h"
Kyle Milka's avatar
Kyle Milka committed
8
#include "android_webview/common/aw_switches.h"
9 10
#include "android_webview/jni/AwMetricsServiceClient_jni.h"
#include "base/android/build_info.h"
paulmiller's avatar
paulmiller committed
11
#include "base/android/jni_string.h"
12 13 14
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/guid.h"
15
#include "base/hash.h"
16
#include "base/i18n/rtl.h"
Kyle Milka's avatar
Kyle Milka committed
17 18
#include "base/lazy_instance.h"
#include "base/path_service.h"
19
#include "base/strings/string16.h"
20
#include "base/task_scheduler/post_task.h"
21 22 23 24 25 26 27 28 29 30
#include "base/threading/sequenced_worker_pool.h"
#include "components/metrics/call_stack_profile_metrics_provider.h"
#include "components/metrics/enabled_state_provider.h"
#include "components/metrics/gpu/gpu_metrics_provider.h"
#include "components/metrics/metrics_log_uploader.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/ui/screen_info_metrics_provider.h"
#include "components/metrics/url_constants.h"
paulmiller's avatar
paulmiller committed
31
#include "components/metrics/version_utils.h"
32
#include "components/prefs/pref_service.h"
paulmiller's avatar
paulmiller committed
33 34
#include "components/version_info/channel_android.h"
#include "components/version_info/version_info.h"
35 36
#include "content/public/browser/browser_thread.h"

37 38
namespace android_webview {

39 40 41 42 43 44
base::LazyInstance<AwMetricsServiceClient>::Leaky g_lazy_instance_;

namespace {

const int kUploadIntervalMinutes = 30;

Kyle Milka's avatar
Kyle Milka committed
45 46 47 48
// A GUID in text form is composed of 32 hex digits and 4 hyphens.
const size_t GUID_SIZE = 32 + 4;

// Client ID of the app, read and cached synchronously at startup
49
base::LazyInstance<std::string>::Leaky g_client_id = LAZY_INSTANCE_INITIALIZER;
Kyle Milka's avatar
Kyle Milka committed
50

51 52 53 54 55 56 57 58 59 60
// Callbacks for metrics::MetricsStateManager::Create. Store/LoadClientInfo
// allow Windows Chrome to back up ClientInfo. They're no-ops for WebView.

void StoreClientInfo(const metrics::ClientInfo& client_info) {}

std::unique_ptr<metrics::ClientInfo> LoadClientInfo() {
  std::unique_ptr<metrics::ClientInfo> client_info;
  return client_info;
}

paulmiller's avatar
paulmiller committed
61 62 63 64 65 66 67 68 69
version_info::Channel GetChannelFromPackageName() {
  JNIEnv* env = base::android::AttachCurrentThread();
  std::string package_name = base::android::ConvertJavaStringToUTF8(
      env, Java_AwMetricsServiceClient_getWebViewPackageName(env));
  // We can't determine the channel for stand-alone WebView, since it has the
  // same package name across channels. It will always be "unknown".
  return version_info::ChannelFromPackageName(package_name.c_str());
}

70 71
// WebView Metrics are sampled based on GUID value.
// TODO(paulmiller) Sample with Finch, once we have Finch.
72
bool IsInSample(const std::string& client_id) {
73 74 75 76 77 78 79
  // client_id comes from base::GenerateGUID(), so its value is random/uniform,
  // except for a few bit positions with fixed values, and some hyphens. Rather
  // than separating the random payload from the fixed bits, just hash the whole
  // thing, to produce a new random/~uniform value.
  uint32_t hash = base::PersistentHash(client_id);

  // Since hashing is ~uniform, the chance that the value falls in the bottom
80 81
  // 2% (1/50th) of possible values is 2%.
  return hash < UINT32_MAX / 50u;
82 83
}

84 85 86 87 88 89 90 91
}  // namespace

// static
AwMetricsServiceClient* AwMetricsServiceClient::GetInstance() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  return g_lazy_instance_.Pointer();
}

92
void AwMetricsServiceClient::LoadOrCreateClientId() {
93 94
  // This function should only be called once at start up.
  DCHECK_NE(g_client_id.Get().length(), GUID_SIZE);
Kyle Milka's avatar
Kyle Milka committed
95 96 97 98 99 100 101 102 103

  // UMA uses randomly-generated GUIDs (globally unique identifiers) to
  // anonymously identify logs. Every WebView-using app on every device
  // is given a GUID, stored in this file in the app's data directory.
  base::FilePath user_data_dir;
  if (!PathService::Get(base::DIR_ANDROID_APP_DATA, &user_data_dir)) {
    LOG(ERROR) << "Failed to get app data directory for Android WebView";

    // Generate a 1-time GUID so metrics can still be collected
104
    g_client_id.Get() = base::GenerateGUID();
105
    return;
Kyle Milka's avatar
Kyle Milka committed
106 107 108 109 110 111
  }

  const base::FilePath guid_file_path =
      user_data_dir.Append(FILE_PATH_LITERAL("metrics_guid"));

  // Try to read an existing GUID.
112
  if (base::ReadFileToStringWithMaxSize(guid_file_path, &g_client_id.Get(),
Kyle Milka's avatar
Kyle Milka committed
113
                                        GUID_SIZE)) {
114
    if (base::IsValidGUID(g_client_id.Get()))
115
      return;
Kyle Milka's avatar
Kyle Milka committed
116 117 118 119
    LOG(ERROR) << "Overwriting invalid GUID";
  }

  // We must write a new GUID.
120 121 122
  g_client_id.Get() = base::GenerateGUID();
  if (!base::WriteFile(guid_file_path, g_client_id.Get().c_str(),
                       g_client_id.Get().size())) {
Kyle Milka's avatar
Kyle Milka committed
123 124 125 126
    // If writing fails, proceed anyway with the new GUID. It won't be persisted
    // to the next run, but we can still collect metrics with this 1-time GUID.
    LOG(ERROR) << "Failed to write new GUID";
  }
127 128 129 130 131 132
}

std::string AwMetricsServiceClient::GetClientId() {
  // This function should only be called if LoadOrCreateClientId() was
  // previously called.
  DCHECK_EQ(g_client_id.Get().length(), GUID_SIZE);
133 134

  return g_client_id.Get();
Kyle Milka's avatar
Kyle Milka committed
135 136
}

137 138
void AwMetricsServiceClient::Initialize(
    PrefService* pref_service,
Kyle Milka's avatar
Kyle Milka committed
139
    net::URLRequestContextGetter* request_context) {
140 141 142 143 144 145
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  DCHECK(pref_service_ == nullptr);  // Initialize should only happen once.
  DCHECK(request_context_ == nullptr);
  pref_service_ = pref_service;
  request_context_ = request_context;
paulmiller's avatar
paulmiller committed
146
  channel_ = GetChannelFromPackageName();
147

148 149
  // If variations are enabled for WebView the GUID will already have been read
  // at startup
Kyle Milka's avatar
Kyle Milka committed
150
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
151
          switches::kEnableWebViewVariations)) {
152
    InitializeWithClientId();
Kyle Milka's avatar
Kyle Milka committed
153
  } else {
154 155
    base::PostTaskWithTraitsAndReply(
        FROM_HERE, {base::MayBlock()},
156
        base::Bind(&AwMetricsServiceClient::LoadOrCreateClientId),
157
        base::Bind(&AwMetricsServiceClient::InitializeWithClientId,
Kyle Milka's avatar
Kyle Milka committed
158 159
                   base::Unretained(this)));
  }
160 161
}

162
void AwMetricsServiceClient::InitializeWithClientId() {
Kyle Milka's avatar
Kyle Milka committed
163
  // The guid must have already been initialized at this point, either
164 165 166
  // synchronously or asynchronously depending on the kEnableWebViewFinch flag
  DCHECK_EQ(g_client_id.Get().length(), GUID_SIZE);
  pref_service_->SetString(metrics::prefs::kMetricsClientID, g_client_id.Get());
167

168
  in_sample_ = IsInSample(g_client_id.Get());
169

170
  metrics_state_manager_ = metrics::MetricsStateManager::Create(
171
      pref_service_, this, base::string16(), base::Bind(&StoreClientInfo),
172 173 174 175 176 177 178
      base::Bind(&LoadClientInfo));

  metrics_service_.reset(new ::metrics::MetricsService(
      metrics_state_manager_.get(), this, pref_service_));

  metrics_service_->RegisterMetricsProvider(
      std::unique_ptr<metrics::MetricsProvider>(
179
          new metrics::NetworkMetricsProvider));
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

  metrics_service_->RegisterMetricsProvider(
      std::unique_ptr<metrics::MetricsProvider>(
          new metrics::GPUMetricsProvider));

  metrics_service_->RegisterMetricsProvider(
      std::unique_ptr<metrics::MetricsProvider>(
          new metrics::ScreenInfoMetricsProvider));

  metrics_service_->RegisterMetricsProvider(
      std::unique_ptr<metrics::MetricsProvider>(
          new metrics::CallStackProfileMetricsProvider));

  metrics_service_->InitializeMetricsRecordingState();

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AwMetricsServiceClient_nativeInitialized(env);
}

bool AwMetricsServiceClient::IsConsentGiven() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
201
  return consent_;
202 203
}

204
bool AwMetricsServiceClient::IsReportingEnabled() {
205
  return consent_ && in_sample_;
206
}
207

208 209 210 211 212 213 214 215 216
void AwMetricsServiceClient::SetHaveMetricsConsent(bool consent) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  consent_ = consent;
  // Receiving this call is the last step in determining whether metrics should
  // be enabled; if so, start metrics. There's no need for a matching Stop()
  // call, since SetHaveMetricsConsent(false) never happens, and WebView has no
  // shutdown sequence.
  if (IsReportingEnabled()) {
    metrics_service_->Start();
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
  }
}

metrics::MetricsService* AwMetricsServiceClient::GetMetricsService() {
  return metrics_service_.get();
}

// In Chrome, UMA and Breakpad are enabled/disabled together by the same
// checkbox and they share the same client ID (a.k.a. GUID). SetMetricsClientId
// is intended to provide the ID to Breakpad. In WebView, UMA and Breakpad are
// independent, so this is a no-op.
void AwMetricsServiceClient::SetMetricsClientId(const std::string& client_id) {}

int32_t AwMetricsServiceClient::GetProduct() {
  return ::metrics::ChromeUserMetricsExtension::ANDROID_WEBVIEW;
}

std::string AwMetricsServiceClient::GetApplicationLocale() {
  return base::i18n::GetConfiguredLocale();
}

bool AwMetricsServiceClient::GetBrand(std::string* brand_code) {
  // WebView doesn't use brand codes.
  return false;
}

metrics::SystemProfileProto::Channel AwMetricsServiceClient::GetChannel() {
paulmiller's avatar
paulmiller committed
244
  return metrics::AsProtobufChannel(channel_);
245 246 247
}

std::string AwMetricsServiceClient::GetVersionString() {
248
  return version_info::GetVersionNumber();
249 250 251 252 253 254 255 256 257 258
}

void AwMetricsServiceClient::CollectFinalMetricsForLog(
    const base::Closure& done_callback) {
  done_callback.Run();
}

std::unique_ptr<metrics::MetricsLogUploader>
AwMetricsServiceClient::CreateUploader(
    base::StringPiece server_url,
259
    base::StringPiece insecure_server_url,
260 261 262
    base::StringPiece mime_type,
    metrics::MetricsLogUploader::MetricServiceType service_type,
    const metrics::MetricsLogUploader::UploadCallback& on_upload_complete) {
263 264 265
  // |server_url|, |insecure_server_url| and |mime_type| are unused because
  // WebView uses the platform logging mechanism instead of the normal UMA
  // server.
266 267 268 269 270 271 272 273 274 275 276
  return std::unique_ptr<::metrics::MetricsLogUploader>(
      new AwMetricsLogUploader(on_upload_complete));
}

base::TimeDelta AwMetricsServiceClient::GetStandardUploadInterval() {
  // The platform logging mechanism is responsible for upload frequency; this
  // just specifies how frequently to provide logs to the platform.
  return base::TimeDelta::FromMinutes(kUploadIntervalMinutes);
}

AwMetricsServiceClient::AwMetricsServiceClient()
277
    : pref_service_(nullptr),
paulmiller's avatar
paulmiller committed
278
      request_context_(nullptr),
279 280 281
      channel_(version_info::Channel::UNKNOWN),
      consent_(false),
      in_sample_(false) {}
282 283 284

AwMetricsServiceClient::~AwMetricsServiceClient() {}

285
// static
286 287 288 289
void SetHaveMetricsConsent(JNIEnv* env,
                           const base::android::JavaParamRef<jclass>& jcaller,
                           jboolean consent) {
  g_lazy_instance_.Pointer()->SetHaveMetricsConsent(consent);
290 291
}

292
}  // namespace android_webview