content_suggestions_metrics.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. // Copyright 2016 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. #include "components/ntp_snippets/content_suggestions_metrics.h"
  5. #include <cmath>
  6. #include <string>
  7. #include <type_traits>
  8. #include "base/metrics/histogram.h"
  9. #include "base/metrics/histogram_functions.h"
  10. #include "base/metrics/histogram_macros.h"
  11. #include "base/metrics/user_metrics.h"
  12. #include "base/strings/stringprintf.h"
  13. namespace ntp_snippets {
  14. namespace metrics {
  15. namespace {
  16. // Keep in sync with MAX_SUGGESTIONS_PER_SECTION in NewTabPageUma.java.
  17. const int kMaxSuggestionsPerCategory = 20;
  18. const int kMaxSuggestionsTotal = 50;
  19. const int kMaxCategories = 10;
  20. const char kHistogramCountOnNtpOpenedIfVisible[] =
  21. "NewTabPage.ContentSuggestions.CountOnNtpOpenedIfVisible";
  22. const char kHistogramSectionCountOnNtpOpened[] =
  23. "NewTabPage.ContentSuggestions.SectionCountOnNtpOpened";
  24. const char kHistogramShown[] = "NewTabPage.ContentSuggestions.Shown";
  25. const char kHistogramShownAge[] = "NewTabPage.ContentSuggestions.ShownAge";
  26. const char kHistogramShownScore[] =
  27. "NewTabPage.ContentSuggestions.ShownScoreNormalized";
  28. const char kHistogramOpened[] = "NewTabPage.ContentSuggestions.Opened";
  29. const char kHistogramOpenedAge[] = "NewTabPage.ContentSuggestions.OpenedAge";
  30. const char kHistogramOpenedCategoryIndex[] =
  31. "NewTabPage.ContentSuggestions.OpenedCategoryIndex";
  32. const char kHistogramOpenedScore[] =
  33. "NewTabPage.ContentSuggestions.OpenedScoreNormalized";
  34. const char kHistogramOpenDisposition[] =
  35. "NewTabPage.ContentSuggestions.OpenDisposition";
  36. const char kHistogramMenuOpened[] = "NewTabPage.ContentSuggestions.MenuOpened";
  37. const char kHistogramMenuOpenedAge[] =
  38. "NewTabPage.ContentSuggestions.MenuOpenedAge";
  39. const char kHistogramMenuOpenedScore[] =
  40. "NewTabPage.ContentSuggestions.MenuOpenedScoreNormalized";
  41. const char kHistogramDismissedUnvisited[] =
  42. "NewTabPage.ContentSuggestions.DismissedUnvisited";
  43. const char kHistogramDismissedVisited[] =
  44. "NewTabPage.ContentSuggestions.DismissedVisited";
  45. const char kHistogramArticlesUsageTimeLocal[] =
  46. "NewTabPage.ContentSuggestions.UsageTimeLocal";
  47. const char kHistogramVisitDuration[] =
  48. "NewTabPage.ContentSuggestions.VisitDuration";
  49. const char kHistogramMoreButtonShown[] =
  50. "NewTabPage.ContentSuggestions.MoreButtonShown";
  51. const char kHistogramMoreButtonClicked[] =
  52. "NewTabPage.ContentSuggestions.MoreButtonClicked";
  53. const char kHistogramMovedUpCategoryNewIndex[] =
  54. "NewTabPage.ContentSuggestions.MovedUpCategoryNewIndex";
  55. const char kHistogramCategoryDismissed[] =
  56. "NewTabPage.ContentSuggestions.CategoryDismissed";
  57. const char kHistogramTimeSinceSuggestionFetched[] =
  58. "NewTabPage.ContentSuggestions.TimeSinceSuggestionFetched";
  59. // Histograms related to prefetching.
  60. const char kHistogramPrefetchedArticleOpenedWhenOffline[] =
  61. "NewTabPage.ContentSuggestions.Opened.Articles.Prefetched.Offline";
  62. // NewTabPage.ContentSuggestions.CountOnNtpOpenedIfVisible.Articles.\
  63. // Prefetched.Offline2 and
  64. // NewTabPage.ContentSuggestions.Shown.Articles.Prefetched.Offline2 are recorded
  65. // in Java to avoid race condition.
  66. const char kPerCategoryHistogramFormat[] = "%s.%s";
  67. // This mostly corresponds to the KnownCategories enum, but it is contiguous
  68. // and contains exactly the values to be recorded in UMA. Don't remove or
  69. // reorder elements, only add new ones at the end (before COUNT), and keep in
  70. // sync with ContentSuggestionsCategory in histograms.xml.
  71. enum class HistogramCategories {
  72. EXPERIMENTAL,
  73. RECENT_TABS_DEPRECATED,
  74. DOWNLOADS_DEPRECATED,
  75. BOOKMARKS_DEPRECATED,
  76. PHYSICAL_WEB_PAGES_DEPRECATED,
  77. FOREIGN_TABS_DEPRECATED,
  78. ARTICLES,
  79. READING_LIST,
  80. CONTEXTUAL,
  81. // Insert new values here!
  82. COUNT
  83. };
  84. HistogramCategories GetHistogramCategory(Category category) {
  85. static_assert(
  86. std::is_same<decltype(category.id()),
  87. typename std::underlying_type<KnownCategories>::type>::value,
  88. "KnownCategories must have the same underlying type as category.id()");
  89. // Note: Since the underlying type of KnownCategories is int, it's legal to
  90. // cast from int to KnownCategories, even if the given value isn't listed in
  91. // the enumeration. The switch still makes sure that all known values are
  92. // listed here.
  93. auto known_category = static_cast<KnownCategories>(category.id());
  94. switch (known_category) {
  95. case KnownCategories::ARTICLES:
  96. return HistogramCategories::ARTICLES;
  97. case KnownCategories::READING_LIST:
  98. return HistogramCategories::READING_LIST;
  99. case KnownCategories::BOOKMARKS_DEPRECATED:
  100. return HistogramCategories::BOOKMARKS_DEPRECATED;
  101. case KnownCategories::DOWNLOADS_DEPRECATED:
  102. return HistogramCategories::DOWNLOADS_DEPRECATED;
  103. case KnownCategories::FOREIGN_TABS_DEPRECATED:
  104. return HistogramCategories::FOREIGN_TABS_DEPRECATED;
  105. case KnownCategories::RECENT_TABS_DEPRECATED:
  106. case KnownCategories::PHYSICAL_WEB_PAGES_DEPRECATED:
  107. case KnownCategories::LOCAL_CATEGORIES_COUNT:
  108. case KnownCategories::REMOTE_CATEGORIES_OFFSET:
  109. NOTREACHED();
  110. return HistogramCategories::COUNT;
  111. }
  112. // All other (unknown) categories go into a single "Experimental" bucket.
  113. return HistogramCategories::EXPERIMENTAL;
  114. }
  115. // Each suffix here should correspond to an entry under histogram suffix
  116. // ContentSuggestionCategory in histograms.xml.
  117. std::string GetCategorySuffix(Category category) {
  118. HistogramCategories histogram_category = GetHistogramCategory(category);
  119. switch (histogram_category) {
  120. case HistogramCategories::ARTICLES:
  121. return "Articles";
  122. case HistogramCategories::EXPERIMENTAL:
  123. return "Experimental";
  124. case HistogramCategories::READING_LIST:
  125. return "ReadingList";
  126. case HistogramCategories::CONTEXTUAL:
  127. return "Contextual";
  128. case HistogramCategories::BOOKMARKS_DEPRECATED:
  129. case HistogramCategories::DOWNLOADS_DEPRECATED:
  130. case HistogramCategories::FOREIGN_TABS_DEPRECATED:
  131. case HistogramCategories::RECENT_TABS_DEPRECATED:
  132. case HistogramCategories::PHYSICAL_WEB_PAGES_DEPRECATED:
  133. case HistogramCategories::COUNT:
  134. NOTREACHED();
  135. break;
  136. }
  137. return std::string();
  138. }
  139. std::string GetCategoryHistogramName(const char* base_name, Category category) {
  140. return base::StringPrintf(kPerCategoryHistogramFormat, base_name,
  141. GetCategorySuffix(category).c_str());
  142. }
  143. // This corresponds to UMA_HISTOGRAM_CUSTOM_TIMES (with min/max appropriate
  144. // for the age of suggestions) for use with dynamic histogram names.
  145. void UmaHistogramAge(const std::string& name, const base::TimeDelta& value) {
  146. base::Histogram::FactoryTimeGet(
  147. name, base::Seconds(1), base::Days(7), 100,
  148. base::HistogramBase::kUmaTargetedHistogramFlag)
  149. ->AddTime(value);
  150. }
  151. void LogCategoryHistogramPosition(const char* base_name,
  152. Category category,
  153. int position,
  154. int max_position) {
  155. std::string name = GetCategoryHistogramName(base_name, category);
  156. // Since the histogram name is dynamic, we can't use the regular macro.
  157. base::UmaHistogramExactLinear(name, position, max_position);
  158. }
  159. void LogCategoryHistogramAge(const char* base_name,
  160. Category category,
  161. const base::TimeDelta& value) {
  162. std::string name = GetCategoryHistogramName(base_name, category);
  163. // Since the histogram name is dynamic, we can't use the regular macro.
  164. UmaHistogramAge(name, value);
  165. }
  166. void LogCategoryHistogramScore(const char* base_name,
  167. Category category,
  168. float score) {
  169. std::string name = GetCategoryHistogramName(base_name, category);
  170. // Scores are typically reported in a range of (0,1]. As UMA does not support
  171. // floats, we put them on a discrete scale of [1,10]. We keep the extra bucket
  172. // 11 for unexpected over-flows as we want to distinguish them from scores
  173. // close to 1. For instance, the discrete value 1 represents score values
  174. // within (0.0, 0.1].
  175. base::UmaHistogramExactLinear(name, ceil(score * 10), 11);
  176. }
  177. // Records ContentSuggestions usage. Therefore the day is sliced into 20min
  178. // buckets. Depending on the current local time the count of the corresponding
  179. // bucket is increased.
  180. void RecordContentSuggestionsUsage() {
  181. const int kBucketSizeMins = 20;
  182. const int kNumBuckets = 24 * 60 / kBucketSizeMins;
  183. base::Time::Exploded now_exploded;
  184. base::Time::Now().LocalExplode(&now_exploded);
  185. int bucket = (now_exploded.hour * 60 + now_exploded.minute) / kBucketSizeMins;
  186. const char* kWeekdayNames[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
  187. "Thursday", "Friday", "Saturday"};
  188. std::string histogram_name(
  189. base::StringPrintf("%s.%s", kHistogramArticlesUsageTimeLocal,
  190. kWeekdayNames[now_exploded.day_of_week]));
  191. base::UmaHistogramExactLinear(histogram_name, bucket, kNumBuckets);
  192. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramArticlesUsageTimeLocal, bucket,
  193. kNumBuckets);
  194. base::RecordAction(
  195. base::UserMetricsAction("NewTabPage_ContentSuggestions_ArticlesUsage"));
  196. }
  197. } // namespace
  198. void OnPageShown(const std::vector<Category>& categories,
  199. const std::vector<int>& suggestions_per_category,
  200. const std::vector<bool>& is_category_visible) {
  201. DCHECK_EQ(categories.size(), suggestions_per_category.size());
  202. DCHECK_EQ(categories.size(), is_category_visible.size());
  203. int suggestions_total = 0;
  204. int visible_categories_count = 0;
  205. for (size_t i = 0; i < categories.size(); ++i) {
  206. if (is_category_visible[i]) {
  207. LogCategoryHistogramPosition(kHistogramCountOnNtpOpenedIfVisible,
  208. categories[i], suggestions_per_category[i],
  209. kMaxSuggestionsPerCategory);
  210. suggestions_total += suggestions_per_category[i];
  211. ++visible_categories_count;
  212. }
  213. }
  214. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramCountOnNtpOpenedIfVisible,
  215. suggestions_total, kMaxSuggestionsTotal);
  216. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramSectionCountOnNtpOpened,
  217. visible_categories_count, kMaxCategories);
  218. }
  219. void OnSuggestionShown(int global_position,
  220. Category category,
  221. int position_in_category,
  222. base::Time publish_date,
  223. float score,
  224. base::Time fetch_date) {
  225. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramShown, global_position,
  226. kMaxSuggestionsTotal);
  227. LogCategoryHistogramPosition(kHistogramShown, category, position_in_category,
  228. kMaxSuggestionsPerCategory);
  229. base::TimeDelta age = base::Time::Now() - publish_date;
  230. LogCategoryHistogramAge(kHistogramShownAge, category, age);
  231. LogCategoryHistogramScore(kHistogramShownScore, category, score);
  232. if (category.IsKnownCategory(KnownCategories::ARTICLES)) {
  233. // Records the time since the fetch time of the displayed snippet.
  234. UMA_HISTOGRAM_CUSTOM_TIMES(kHistogramTimeSinceSuggestionFetched,
  235. base::Time::Now() - fetch_date, base::Seconds(1),
  236. base::Days(7),
  237. /*bucket_count=*/100);
  238. }
  239. // TODO(markusheintz): Discuss whether the code below should be moved into a
  240. // separate method called OnSuggestionsListShown.
  241. // When the first of the articles suggestions is shown, then we count this as
  242. // a single usage of content suggestions.
  243. if (category.IsKnownCategory(KnownCategories::ARTICLES) &&
  244. position_in_category == 0) {
  245. RecordContentSuggestionsUsage();
  246. }
  247. }
  248. void OnSuggestionOpened(int global_position,
  249. Category category,
  250. int category_index,
  251. int position_in_category,
  252. base::Time publish_date,
  253. float score,
  254. WindowOpenDisposition disposition,
  255. bool is_prefetched,
  256. bool is_offline) {
  257. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramOpenedCategoryIndex, category_index,
  258. kMaxCategories);
  259. LogCategoryHistogramPosition(kHistogramOpenedCategoryIndex, category,
  260. category_index, kMaxCategories);
  261. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramOpened, global_position,
  262. kMaxSuggestionsTotal);
  263. LogCategoryHistogramPosition(kHistogramOpened, category, position_in_category,
  264. kMaxSuggestionsPerCategory);
  265. base::TimeDelta age = base::Time::Now() - publish_date;
  266. LogCategoryHistogramAge(kHistogramOpenedAge, category, age);
  267. LogCategoryHistogramScore(kHistogramOpenedScore, category, score);
  268. // We use WindowOpenDisposition::MAX_VALUE + 1 for |value_max| since MAX_VALUE
  269. // itself is a valid (and used) enum value.
  270. UMA_HISTOGRAM_EXACT_LINEAR(
  271. kHistogramOpenDisposition, static_cast<int>(disposition),
  272. static_cast<int>(WindowOpenDisposition::MAX_VALUE) + 1);
  273. base::UmaHistogramExactLinear(
  274. GetCategoryHistogramName(kHistogramOpenDisposition, category),
  275. static_cast<int>(disposition),
  276. static_cast<int>(WindowOpenDisposition::MAX_VALUE) + 1);
  277. if (category.IsKnownCategory(KnownCategories::ARTICLES)) {
  278. RecordContentSuggestionsUsage();
  279. if (is_offline && is_prefetched) {
  280. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramPrefetchedArticleOpenedWhenOffline,
  281. position_in_category,
  282. kMaxSuggestionsPerCategory);
  283. }
  284. }
  285. base::RecordAction(base::UserMetricsAction("Suggestions.Content.Opened"));
  286. }
  287. void OnSuggestionMenuOpened(int global_position,
  288. Category category,
  289. int position_in_category,
  290. base::Time publish_date,
  291. float score) {
  292. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramMenuOpened, global_position,
  293. kMaxSuggestionsTotal);
  294. LogCategoryHistogramPosition(kHistogramMenuOpened, category,
  295. position_in_category,
  296. kMaxSuggestionsPerCategory);
  297. base::TimeDelta age = base::Time::Now() - publish_date;
  298. LogCategoryHistogramAge(kHistogramMenuOpenedAge, category, age);
  299. LogCategoryHistogramScore(kHistogramMenuOpenedScore, category, score);
  300. }
  301. void OnSuggestionDismissed(int global_position,
  302. Category category,
  303. int position_in_category,
  304. bool visited) {
  305. if (visited) {
  306. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramDismissedVisited, global_position,
  307. kMaxSuggestionsTotal);
  308. LogCategoryHistogramPosition(kHistogramDismissedVisited, category,
  309. position_in_category,
  310. kMaxSuggestionsPerCategory);
  311. } else {
  312. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramDismissedUnvisited, global_position,
  313. kMaxSuggestionsTotal);
  314. LogCategoryHistogramPosition(kHistogramDismissedUnvisited, category,
  315. position_in_category,
  316. kMaxSuggestionsPerCategory);
  317. }
  318. }
  319. void OnSuggestionTargetVisited(Category category, base::TimeDelta visit_time) {
  320. std::string name =
  321. GetCategoryHistogramName(kHistogramVisitDuration, category);
  322. base::UmaHistogramLongTimes(name, visit_time);
  323. }
  324. void OnCategoryMovedUp(int new_index) {
  325. UMA_HISTOGRAM_EXACT_LINEAR(kHistogramMovedUpCategoryNewIndex, new_index,
  326. kMaxCategories);
  327. }
  328. void OnMoreButtonShown(Category category, int position) {
  329. // The "more" card can appear in addition to the actual suggestions, so add
  330. // one extra bucket to this histogram.
  331. LogCategoryHistogramPosition(kHistogramMoreButtonShown, category, position,
  332. kMaxSuggestionsPerCategory + 1);
  333. }
  334. void OnMoreButtonClicked(Category category, int position) {
  335. // The "more" card can appear in addition to the actual suggestions, so add
  336. // one extra bucket to this histogram.
  337. LogCategoryHistogramPosition(kHistogramMoreButtonClicked, category, position,
  338. kMaxSuggestionsPerCategory + 1);
  339. }
  340. void OnCategoryDismissed(Category category) {
  341. UMA_HISTOGRAM_ENUMERATION(kHistogramCategoryDismissed,
  342. GetHistogramCategory(category),
  343. HistogramCategories::COUNT);
  344. }
  345. void RecordRemoteSuggestionsProviderState(bool enabled) {
  346. UMA_HISTOGRAM_BOOLEAN(
  347. "NewTabPage.ContentSuggestions.Preferences.RemoteSuggestions", enabled);
  348. }
  349. void RecordContentSuggestionDismissed() {
  350. base::RecordAction(base::UserMetricsAction("Suggestions.Content.Dismissed"));
  351. }
  352. void RecordCategoryDismissed() {
  353. base::RecordAction(base::UserMetricsAction("Suggestions.Category.Dismissed"));
  354. }
  355. void RecordFetchAction() {
  356. base::RecordAction(base::UserMetricsAction("Suggestions.Category.Fetch"));
  357. }
  358. } // namespace metrics
  359. } // namespace ntp_snippets