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