extensions_metrics_provider.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
1// Copyright 2014 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#include "chrome/browser/metrics/extensions_metrics_provider.h"
6
7#include <set>
8
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/strings/stringprintf.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/extensions/install_verifier.h"
14#include "chrome/browser/profiles/profile_manager.h"
15#include "components/metrics/metrics_log.h"
16#include "components/metrics/metrics_state_manager.h"
17#include "components/metrics/proto/system_profile.pb.h"
18#include "extensions/browser/extension_registry.h"
19#include "extensions/browser/extension_system.h"
20#include "extensions/common/extension_set.h"
21#include "third_party/smhasher/src/City.h"
22
23namespace {
24
25// The number of possible hash keys that a client may use.  The UMA client_id
26// value is reduced modulo this value to produce the key used by that
27// particular client.
28const size_t kExtensionListClientKeys = 4096;
29
30// The number of hash buckets into which extension IDs are mapped.  This sets
31// the possible output range of the HashExtension function.
32const size_t kExtensionListBuckets = 1024;
33
34// Possible states for extensions. The order of these enum values is important,
35// and is used when combining the state of multiple extensions and multiple
36// profiles. Combining two states should always result in the higher state.
37// Ex: One profile is in state FROM_STORE_VERIFIED, and another is in
38// FROM_STORE_UNVERIFIED. The state of the two profiles together will be
39// FROM_STORE_UNVERIFIED.
40// This enum should be kept in sync with the corresponding enum in
41// components/metrics/proto/system_profile.proto
42enum ExtensionState {
43  NO_EXTENSIONS,
44  FROM_STORE_VERIFIED,
45  FROM_STORE_UNVERIFIED,
46  OFF_STORE
47};
48
49metrics::SystemProfileProto::ExtensionsState ExtensionStateAsProto(
50    ExtensionState value) {
51  switch (value) {
52    case NO_EXTENSIONS:
53      return metrics::SystemProfileProto::NO_EXTENSIONS;
54    case FROM_STORE_VERIFIED:
55      return metrics::SystemProfileProto::NO_OFFSTORE_VERIFIED;
56    case FROM_STORE_UNVERIFIED:
57      return metrics::SystemProfileProto::NO_OFFSTORE_UNVERIFIED;
58    case OFF_STORE:
59      return metrics::SystemProfileProto::HAS_OFFSTORE;
60  }
61  NOTREACHED();
62  return metrics::SystemProfileProto::NO_EXTENSIONS;
63}
64
65// Determines if the |extension| is an extension (can use extension APIs) and is
66// not from the webstore. If local information claims the extension is from the
67// webstore, we attempt to verify with |verifier| by checking if it has been
68// explicitly deemed invalid. If |verifier| is inactive or if the extension is
69// unknown to |verifier|, the local information is trusted.
70ExtensionState IsOffStoreExtension(
71    const extensions::Extension& extension,
72    const extensions::InstallVerifier& verifier) {
73  if (!extension.is_extension() && !extension.is_legacy_packaged_app())
74    return NO_EXTENSIONS;
75
76  // Component extensions are considered safe.
77  if (extensions::Manifest::IsComponentLocation(extension.location()))
78    return NO_EXTENSIONS;
79
80  if (verifier.AllowedByEnterprisePolicy(extension.id()))
81    return NO_EXTENSIONS;
82
83  if (!extensions::InstallVerifier::IsFromStore(extension))
84    return OFF_STORE;
85
86  // Local information about the extension implies it is from the store. We try
87  // to use the install verifier to verify this.
88  if (!verifier.IsKnownId(extension.id()))
89    return FROM_STORE_UNVERIFIED;
90
91  if (verifier.IsInvalid(extension.id()))
92    return OFF_STORE;
93
94  return FROM_STORE_VERIFIED;
95}
96
97// Finds the ExtensionState of |extensions|. The return value will be the
98// highest (as defined by the order of ExtensionState) value of each extension
99// in |extensions|.
100ExtensionState CheckForOffStore(const extensions::ExtensionSet& extensions,
101                                const extensions::InstallVerifier& verifier) {
102  ExtensionState state = NO_EXTENSIONS;
103  for (extensions::ExtensionSet::const_iterator it = extensions.begin();
104       it != extensions.end() && state < OFF_STORE;
105       ++it) {
106    // Combine the state of each extension, always favoring the higher state as
107    // defined by the order of ExtensionState.
108    state = std::max(state, IsOffStoreExtension(**it, verifier));
109  }
110  return state;
111}
112
113}  // namespace
114
115ExtensionsMetricsProvider::ExtensionsMetricsProvider(
116    metrics::MetricsStateManager* metrics_state_manager)
117    : metrics_state_manager_(metrics_state_manager), cached_profile_(NULL) {
118  DCHECK(metrics_state_manager_);
119}
120
121ExtensionsMetricsProvider::~ExtensionsMetricsProvider() {
122}
123
124// static
125int ExtensionsMetricsProvider::HashExtension(const std::string& extension_id,
126                                             uint32 client_key) {
127  DCHECK_LE(client_key, kExtensionListClientKeys);
128  std::string message =
129      base::StringPrintf("%u:%s", client_key, extension_id.c_str());
130  uint64 output = CityHash64(message.data(), message.size());
131  return output % kExtensionListBuckets;
132}
133
134Profile* ExtensionsMetricsProvider::GetMetricsProfile() {
135  ProfileManager* profile_manager = g_browser_process->profile_manager();
136  if (!profile_manager)
137    return NULL;
138
139  // If there is a cached profile, reuse that.  However, check that it is still
140  // valid first.
141  if (cached_profile_ && profile_manager->IsValidProfile(cached_profile_))
142    return cached_profile_;
143
144  // Find a suitable profile to use, and cache it so that we continue to report
145  // statistics on the same profile.  We would simply use
146  // ProfileManager::GetLastUsedProfile(), except that that has the side effect
147  // of creating a profile if it does not yet exist.
148  cached_profile_ = profile_manager->GetProfileByPath(
149      profile_manager->GetLastUsedProfileDir(profile_manager->user_data_dir()));
150  if (cached_profile_) {
151    // Ensure that the returned profile is not an incognito profile.
152    cached_profile_ = cached_profile_->GetOriginalProfile();
153  }
154  return cached_profile_;
155}
156
157scoped_ptr<extensions::ExtensionSet>
158ExtensionsMetricsProvider::GetInstalledExtensions(Profile* profile) {
159  if (profile) {
160    return extensions::ExtensionRegistry::Get(profile)
161        ->GenerateInstalledExtensionsSet();
162  }
163  return scoped_ptr<extensions::ExtensionSet>();
164}
165
166uint64 ExtensionsMetricsProvider::GetClientID() {
167  // TODO(blundell): Create a MetricsLog::ClientIDAsInt() API and call it
168  // here as well as in MetricsLog's population of the client_id field of
169  // the uma_proto.
170  return MetricsLog::Hash(metrics_state_manager_->client_id());
171}
172
173void ExtensionsMetricsProvider::ProvideSystemProfileMetrics(
174    metrics::SystemProfileProto* system_profile) {
175  ProvideOffStoreMetric(system_profile);
176  ProvideOccupiedBucketMetric(system_profile);
177}
178
179void ExtensionsMetricsProvider::ProvideOffStoreMetric(
180    metrics::SystemProfileProto* system_profile) {
181  ProfileManager* profile_manager = g_browser_process->profile_manager();
182  if (!profile_manager)
183    return;
184
185  ExtensionState state = NO_EXTENSIONS;
186
187  // The off-store metric includes information from all loaded profiles at the
188  // time when this metric is generated.
189  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
190  for (size_t i = 0u; i < profiles.size() && state < OFF_STORE; ++i) {
191    extensions::InstallVerifier* verifier =
192        extensions::ExtensionSystem::Get(profiles[i])->install_verifier();
193
194    scoped_ptr<extensions::ExtensionSet> extensions(
195        GetInstalledExtensions(profiles[i]));
196    if (!extensions)
197      continue;
198
199    // Combine the state from each profile, always favoring the higher state as
200    // defined by the order of ExtensionState.
201    state = std::max(state, CheckForOffStore(*extensions.get(), *verifier));
202  }
203
204  system_profile->set_offstore_extensions_state(ExtensionStateAsProto(state));
205}
206
207void ExtensionsMetricsProvider::ProvideOccupiedBucketMetric(
208    metrics::SystemProfileProto* system_profile) {
209  // UMA reports do not support multiple profiles, but extensions are installed
210  // per-profile.  We return the extensions installed in the primary profile.
211  // In the future, we might consider reporting data about extensions in all
212  // profiles.
213  Profile* profile = GetMetricsProfile();
214
215  scoped_ptr<extensions::ExtensionSet> extensions(
216      GetInstalledExtensions(profile));
217  if (!extensions)
218    return;
219
220  const int client_key = GetClientID() % kExtensionListClientKeys;
221
222  std::set<int> buckets;
223  for (extensions::ExtensionSet::const_iterator it = extensions->begin();
224       it != extensions->end();
225       ++it) {
226    buckets.insert(HashExtension((*it)->id(), client_key));
227  }
228
229  for (std::set<int>::const_iterator it = buckets.begin(); it != buckets.end();
230       ++it) {
231    system_profile->add_occupied_extension_bucket(*it);
232  }
233}
234