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