Commit 3cced99b authored by Paul Dyson's avatar Paul Dyson Committed by Commit Bot

Add more click metrics for app launches.

* Add more metrics to UKM event AppListAppLaunch.
* Add a new UKM metric AppListAppClickData to send the
accumulated click data for an app. On an app launch click,
an AppListAppClickData event is sent for the app
clicked on and for five other apps chosen at random.

Also makes user_activity_ukm_logger_helpers a public_dep so
it can be used here.

Bug: 899123
Change-Id: Ie1d6f17673b9548ef6823d05a362fea8992126b9
Reviewed-on: https://chromium-review.googlesource.com/c/1460750Reviewed-by: default avatarSteven Holte <holte@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarJia Meng <jiameng@chromium.org>
Commit-Queue: Paul Dyson <pdyson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635827}
parent 1e93e6fd
...@@ -27,6 +27,7 @@ source_set("chromeos") { ...@@ -27,6 +27,7 @@ source_set("chromeos") {
"//chrome/app/resources:platform_locale_settings", "//chrome/app/resources:platform_locale_settings",
"//chrome/app/theme:chrome_unscaled_resources", "//chrome/app/theme:chrome_unscaled_resources",
"//chrome/app/theme:theme_resources", "//chrome/app/theme:theme_resources",
"//chrome/browser/chromeos/power/ml:user_activity_ukm_logger_helpers",
"//chromeos/dbus:cicerone_proto", "//chromeos/dbus:cicerone_proto",
"//chromeos/dbus:concierge_proto", "//chromeos/dbus:concierge_proto",
"//chromeos/dbus:power_manager_proto", "//chromeos/dbus:power_manager_proto",
...@@ -57,7 +58,6 @@ source_set("chromeos") { ...@@ -57,7 +58,6 @@ source_set("chromeos") {
"//chrome/app:command_ids", "//chrome/app:command_ids",
"//chrome/browser/apps/platform_apps", "//chrome/browser/apps/platform_apps",
"//chrome/browser/apps/platform_apps/api", "//chrome/browser/apps/platform_apps/api",
"//chrome/browser/chromeos/power/ml:user_activity_ukm_logger_helpers",
"//chrome/browser/chromeos/power/ml/smart_dim", "//chrome/browser/chromeos/power/ml/smart_dim",
"//chrome/browser/devtools", "//chrome/browser/devtools",
"//chrome/browser/extensions", "//chrome/browser/extensions",
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_UI_APP_LIST_APP_LAUNCH_EVENT_LOGGER_H_ #ifndef CHROME_BROWSER_UI_APP_LIST_APP_LAUNCH_EVENT_LOGGER_H_
#define CHROME_BROWSER_UI_APP_LIST_APP_LAUNCH_EVENT_LOGGER_H_ #define CHROME_BROWSER_UI_APP_LIST_APP_LAUNCH_EVENT_LOGGER_H_
#include <memory>
#include <string>
#include <vector> #include <vector>
#include "base/containers/flat_map.h" #include "base/containers/flat_map.h"
...@@ -14,6 +16,21 @@ ...@@ -14,6 +16,21 @@
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/ui/app_list/app_launch_event_logger.pb.h" #include "chrome/browser/ui/app_list/app_launch_event_logger.pb.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace chromeos {
namespace power {
namespace ml {
class RecentEventsCounter;
} // namespace ml
} // namespace power
} // namespace chromeos
namespace ukm {
namespace builders {
class AppListAppClickData;
} // namespace builders
} // namespace ukm
namespace app_list { namespace app_list {
...@@ -24,15 +41,19 @@ namespace app_list { ...@@ -24,15 +41,19 @@ namespace app_list {
// apps the keys are based upon the app id, for Arc apps the keys are based upon // apps the keys are based upon the app id, for Arc apps the keys are based upon
// a hash of the package name and for PWAs the keys are the urls associated with // a hash of the package name and for PWAs the keys are the urls associated with
// the PWA. // the PWA.
// At the time of app launch this class logs metrics about the app clicked on
// and another five apps that were not clicked on, chosen at random.
class AppLaunchEventLogger { class AppLaunchEventLogger {
public: public:
AppLaunchEventLogger(); AppLaunchEventLogger();
~AppLaunchEventLogger(); ~AppLaunchEventLogger();
// Processes a click on an app in the suggestion chip and logs the resulting // Processes a click on an app in the suggestion chip and logs the resulting
// metrics in UKM. // metrics in UKM. This method calls EnforceLoggingPolicy() to ensure the
// logging policy is complied with.
void OnSuggestionChipClicked(const std::string& id, int suggestion_index); void OnSuggestionChipClicked(const std::string& id, int suggestion_index);
// Processes a click on an app located in the grid of apps in the launcher and // Processes a click on an app located in the grid of apps in the launcher and
// logs the resulting metrics in UKM. // logs the resulting metrics in UKM. This method calls EnforceLoggingPolicy()
// to ensure the logging policy is complied with.
void OnGridClicked(const std::string& id); void OnGridClicked(const std::string& id);
// Provides values to be used when testing. // Provides values to be used when testing.
void SetAppDataForTesting(extensions::ExtensionRegistry* registry, void SetAppDataForTesting(extensions::ExtensionRegistry* registry,
...@@ -43,7 +64,7 @@ class AppLaunchEventLogger { ...@@ -43,7 +64,7 @@ class AppLaunchEventLogger {
static const char kShouldSync[]; static const char kShouldSync[];
private: private:
// Remove any leading "chrome-extension://" or "arc://". Also remove any // Removes any leading "chrome-extension://" or "arc://". Also remove any
// trailing "/". // trailing "/".
std::string RemoveScheme(const std::string& id); std::string RemoveScheme(const std::string& id);
// Creates the mapping from PWA app id to PWA url. This mapping also acts as a // Creates the mapping from PWA app id to PWA url. This mapping also acts as a
...@@ -52,18 +73,33 @@ class AppLaunchEventLogger { ...@@ -52,18 +73,33 @@ class AppLaunchEventLogger {
// Gets the PWA url from its app id. Returns base::EmptyString() if no match // Gets the PWA url from its app id. Returns base::EmptyString() if no match
// found. // found.
const std::string& GetPwaUrl(const std::string& id); const std::string& GetPwaUrl(const std::string& id);
// Sets the |event|'s app type based on the id. Also sets the url for PWA apps // Marks app as ok for policy compliance. If the app is not in
// and package name for Arc apps. // |app_features_map_| then add it.
// Logging policy is enforced here by only assigning a meaningful app type to void OkApp(AppLaunchEvent_AppType app_type,
// apps that are allowed to be logged. An app type of OTHER is assigned to const std::string& app_id,
// apps that cannot be logged by policy. const std::string& arc_package_name,
void SetAppInfo(AppLaunchEvent* event); const std::string& pwa_url);
// Loads the information used to determine which apps can be logged.
void LoadInstalledAppInformation(); // Enforces logging policy, ensuring that the |app_features_map_| only
// Gets the Arc package name for synced apps. Returns base::EmptyString() if // contains apps that are allowed to be logged. All apps are rechecked in case
// app not being synced or app id not found. // they have been uninstalled since the previous check.
std::string GetSyncedArcPackage(const std::string& id); void EnforceLoggingPolicy();
bool IsChromeAppFromWebstore(const std::string&); // Updates the app data following a click.
void ProcessClick(const AppLaunchEvent& event, const base::Time& now);
// Returns a source id. |arc_package_name| is only required for Arc apps,
// |pwa_url| is only required for PWA apps.
ukm::SourceId GetSourceId(AppLaunchEvent_AppType app_type,
const std::string& app_id,
const std::string& arc_package_name,
const std::string& pwa_url);
// Randomly chooses up to five apps to log, plus the app clicked on.
std::vector<std::string> ChooseAppsToLog(const std::string clicked_app_id);
// Records a UMA histogram of the app type clicked on.
void RecordAppTypeClicked(AppLaunchEvent_AppType app_type);
// Helper function to log the clicks each hour metrics.
void LogClicksEachHour(
const AppLaunchFeatures& app_launch_features,
ukm::builders::AppListAppClickData* const app_click_data);
// Logs the app click using UKM. // Logs the app click using UKM.
void Log(AppLaunchEvent app_launch_event); void Log(AppLaunchEvent app_launch_event);
...@@ -77,6 +113,31 @@ class AppLaunchEventLogger { ...@@ -77,6 +113,31 @@ class AppLaunchEventLogger {
const base::DictionaryValue* arc_packages_; const base::DictionaryValue* arc_packages_;
// The Chrome extension registry. // The Chrome extension registry.
extensions::ExtensionRegistry* registry_; extensions::ExtensionRegistry* registry_;
// A map from app id to features. Only contains apps satisfying logging
// policy.
base::flat_map<std::string, AppLaunchFeatures> app_features_map_;
// A map from app id to a counter of the number of clicks in the last hour.
// Has a time resolution one minute.
base::flat_map<std::string,
std::unique_ptr<chromeos::power::ml::RecentEventsCounter>>
app_clicks_last_hour_;
// A map from app id to a counter of the number of clicks in the last 24
// hours. Has a time resolution of 15 minutes.
base::flat_map<std::string,
std::unique_ptr<chromeos::power::ml::RecentEventsCounter>>
app_clicks_last_24_hours_;
// The time this class was instantiated. Allows duration to be calculated.
base::Time start_time_;
// A counter for the click in the last hour. Has a time resolution of 1
// minute.
const std::unique_ptr<chromeos::power::ml::RecentEventsCounter>
all_clicks_last_hour_;
// A counter for the clicks in the last 24 hours. Has a time resolution of 15
// minutes.
const std::unique_ptr<chromeos::power::ml::RecentEventsCounter>
all_clicks_last_24_hours_;
// Used to prevent overwriting of parameters that are set for tests. // Used to prevent overwriting of parameters that are set for tests.
bool testing_ = false; bool testing_ = false;
......
...@@ -35,3 +35,27 @@ message AppLaunchEvent { ...@@ -35,3 +35,27 @@ message AppLaunchEvent {
// The URL for PWA apps. // The URL for PWA apps.
optional string pwa_url = 6; optional string pwa_url = 6;
} }
// The App features used for training and inference.
message AppLaunchFeatures {
// The id of the app as a 32 character string. e.g.
// "pjkljhegncpnkpknbcohdijeoejaedia".
optional string app_id = 1;
optional AppLaunchEvent.AppType app_type = 2;
optional string arc_package_name = 3;
optional string pwa_url = 4;
optional int32 sequence_number = 5;
optional int32 most_recently_used_index = 6;
// Not logged in UKM.
optional int64 time_of_last_click_sec = 7;
optional int32 time_since_last_click_sec = 8;
optional int32 clicks_last_hour = 9;
optional int32 clicks_last_24_hours = 10;
optional int32 total_clicks = 11;
repeated int32 clicks_each_hour = 12;
optional AppLaunchEvent.LaunchedFrom last_launched_from = 13;
// Used when checking app for compliance. Should always be true outside of the
// checking procedure, which removes apps with |is_policy_compliant| false.
// Not logged in UKM.
optional bool is_policy_compliant = 14;
}
...@@ -4,9 +4,12 @@ ...@@ -4,9 +4,12 @@
#include "chrome/browser/ui/app_list/app_launch_event_logger.h" #include "chrome/browser/ui/app_list/app_launch_event_logger.h"
#include <memory>
#include "base/bind.h" #include "base/bind.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
#include "components/arc/arc_prefs.h" #include "components/arc/arc_prefs.h"
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
...@@ -46,10 +49,15 @@ class AppLaunchEventLoggerTest : public testing::Test { ...@@ -46,10 +49,15 @@ class AppLaunchEventLoggerTest : public testing::Test {
}; };
TEST_F(AppLaunchEventLoggerTest, CheckUkmCodePWA) { TEST_F(AppLaunchEventLoggerTest, CheckUkmCodePWA) {
extensions::ExtensionRegistry registry(nullptr);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test").SetID(kPhotosPWAApp).Build();
registry.AddEnabled(extension);
GURL url("https://photos.google.com/"); GURL url("https://photos.google.com/");
AppLaunchEventLogger app_launch_event_logger_; AppLaunchEventLogger app_launch_event_logger_;
app_launch_event_logger_.SetAppDataForTesting(nullptr, nullptr, nullptr); app_launch_event_logger_.SetAppDataForTesting(&registry, nullptr, nullptr);
app_launch_event_logger_.OnGridClicked(kPhotosPWAApp); app_launch_event_logger_.OnGridClicked(kPhotosPWAApp);
scoped_task_environment_.RunUntilIdle(); scoped_task_environment_.RunUntilIdle();
...@@ -58,6 +66,20 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodePWA) { ...@@ -58,6 +66,20 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodePWA) {
ASSERT_EQ(1ul, entries.size()); ASSERT_EQ(1ul, entries.size());
const auto* entry = entries.back(); const auto* entry = entries.back();
test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url); test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url);
test_ukm_recorder_.ExpectEntryMetric(entry, "AllClicksLast24Hours", 1);
test_ukm_recorder_.ExpectEntryMetric(entry, "AllClicksLastHour", 1);
test_ukm_recorder_.ExpectEntryMetric(entry, "AppType", 3);
test_ukm_recorder_.ExpectEntryMetric(entry, "LaunchedFrom", 1);
test_ukm_recorder_.ExpectEntryMetric(entry, "TotalHours", 0);
const auto click_entries =
test_ukm_recorder_.GetEntriesByName("AppListAppClickData");
ASSERT_EQ(1ul, click_entries.size());
const auto* photos_entry = click_entries.back();
test_ukm_recorder_.ExpectEntrySourceHasUrl(photos_entry, url);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "AppLaunchId",
entry->source_id);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "AppType", 3);
} }
TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeChrome) { TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeChrome) {
...@@ -85,6 +107,8 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeChrome) { ...@@ -85,6 +107,8 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeChrome) {
ASSERT_EQ(1ul, entries.size()); ASSERT_EQ(1ul, entries.size());
const auto* entry = entries.back(); const auto* entry = entries.back();
test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url); test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url);
test_ukm_recorder_.ExpectEntryMetric(entry, "AppType", 1);
test_ukm_recorder_.ExpectEntryMetric(entry, "LaunchedFrom", 1);
} }
TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeArc) { TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeArc) {
...@@ -113,13 +137,95 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeArc) { ...@@ -113,13 +137,95 @@ TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeArc) {
ASSERT_EQ(1ul, entries.size()); ASSERT_EQ(1ul, entries.size());
const auto* entry = entries.back(); const auto* entry = entries.back();
test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url); test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, url);
test_ukm_recorder_.ExpectEntryMetric(entry, "AppType", 2);
test_ukm_recorder_.ExpectEntryMetric(entry, "LaunchedFrom", 1);
}
TEST_F(AppLaunchEventLoggerTest, CheckMultipleClicks) {
// Click on PWA photos, then Chrome Maps, then PWA Photos again.
extensions::ExtensionRegistry registry(nullptr);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test").SetID(kPhotosPWAApp).Build();
registry.AddEnabled(extension);
GURL photos_url("https://photos.google.com/");
base::Value package(base::Value::Type::DICTIONARY);
package.SetKey(AppLaunchEventLogger::kShouldSync, base::Value(true));
auto packages = std::make_unique<base::DictionaryValue>();
packages->SetKey(kMapsPackageName, package.Clone());
base::Value app(base::Value::Type::DICTIONARY);
app.SetKey(AppLaunchEventLogger::kPackageName, base::Value(kMapsPackageName));
auto arc_apps = std::make_unique<base::DictionaryValue>();
arc_apps->SetKey(kMapsArcApp, app.Clone());
GURL maps_url("app://play/gbpfhehadcpcndihhameeacbdmbjbhgi");
AppLaunchEventLogger app_launch_event_logger_;
app_launch_event_logger_.SetAppDataForTesting(&registry, arc_apps.get(),
packages.get());
app_launch_event_logger_.OnGridClicked(kPhotosPWAApp);
app_launch_event_logger_.OnGridClicked(kMapsArcApp);
app_launch_event_logger_.OnSuggestionChipClicked(kPhotosPWAApp, 2);
app_launch_event_logger_.OnGridClicked(kPhotosPWAApp);
scoped_task_environment_.RunUntilIdle();
const auto entries = test_ukm_recorder_.GetEntriesByName("AppListAppLaunch");
ASSERT_EQ(4ul, entries.size());
const auto* entry = entries.back();
test_ukm_recorder_.ExpectEntrySourceHasUrl(entry, photos_url);
test_ukm_recorder_.ExpectEntryMetric(entry, "AllClicksLast24Hours", 4);
test_ukm_recorder_.ExpectEntryMetric(entry, "AllClicksLastHour", 4);
test_ukm_recorder_.ExpectEntryMetric(entry, "AppType", 3);
test_ukm_recorder_.ExpectEntryMetric(entry, "LaunchedFrom", 1);
test_ukm_recorder_.ExpectEntryMetric(entry, "TotalHours", 0);
const auto click_entries =
test_ukm_recorder_.GetEntriesByName("AppListAppClickData");
ASSERT_EQ(8ul, click_entries.size());
// Examine the last two events, which are created by the last click.
const auto* maps_entry = click_entries.at(6);
const auto* photos_entry = click_entries.at(7);
if (test_ukm_recorder_.GetSourceForSourceId(maps_entry->source_id)->url() ==
photos_url) {
const auto* tmp_entry = photos_entry;
photos_entry = maps_entry;
maps_entry = tmp_entry;
}
test_ukm_recorder_.ExpectEntrySourceHasUrl(photos_entry, photos_url);
test_ukm_recorder_.ExpectEntrySourceHasUrl(maps_entry, maps_url);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "AppLaunchId",
entry->source_id);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "AppLaunchId",
entry->source_id);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "ClicksLast24Hours", 2);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "ClicksLast24Hours", 1);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "ClicksLastHour", 2);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "ClicksLastHour", 1);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "MostRecentlyUsedIndex",
0);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "MostRecentlyUsedIndex", 1);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "TimeSinceLastClick", 0);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "TotalClicks", 2);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "TotalClicks", 1);
test_ukm_recorder_.ExpectEntryMetric(photos_entry, "LastLaunchedFrom", 2);
test_ukm_recorder_.ExpectEntryMetric(maps_entry, "LastLaunchedFrom", 1);
} }
TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeSuggestionChip) { TEST_F(AppLaunchEventLoggerTest, CheckUkmCodeSuggestionChip) {
extensions::ExtensionRegistry registry(nullptr);
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test").SetID(kPhotosPWAApp).Build();
registry.AddEnabled(extension);
GURL url("https://photos.google.com/"); GURL url("https://photos.google.com/");
AppLaunchEventLogger app_launch_event_logger_; AppLaunchEventLogger app_launch_event_logger_;
app_launch_event_logger_.SetAppDataForTesting(nullptr, nullptr, nullptr); app_launch_event_logger_.SetAppDataForTesting(&registry, nullptr, nullptr);
app_launch_event_logger_.OnSuggestionChipClicked(kPhotosPWAApp, 2); app_launch_event_logger_.OnSuggestionChipClicked(kPhotosPWAApp, 2);
scoped_task_environment_.RunUntilIdle(); scoped_task_environment_.RunUntilIdle();
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment