Commit 41c6b0f7 authored by Min Qin's avatar Min Qin Committed by Commit Bot

add metadata for calculating tile score

This CL adds a metadata to existing TileGroup proto.
And store it in the Tile DB.
To avoid being removed during tile download, this TileGroup
is using a special key and saved separately.
A utility class is introduced to sort the tiles.

Bug: 1096224
Change-Id: I81f4471e5e89794d3538a4b7522c6b22c36e15b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417088
Commit-Queue: Min Qin <qinmin@chromium.org>
Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#808193}
parent 0fbe4abc
...@@ -45,6 +45,8 @@ source_set("internal") { ...@@ -45,6 +45,8 @@ source_set("internal") {
"tile_store.cc", "tile_store.cc",
"tile_store.h", "tile_store.h",
"tile_types.h", "tile_types.h",
"tile_utils.cc",
"tile_utils.h",
] ]
public_deps = [ "//components/image_fetcher/core" ] public_deps = [ "//components/image_fetcher/core" ]
...@@ -80,6 +82,7 @@ source_set("unit_tests") { ...@@ -80,6 +82,7 @@ source_set("unit_tests") {
"tile_service_impl_unittest.cc", "tile_service_impl_unittest.cc",
"tile_service_scheduler_unittest.cc", "tile_service_scheduler_unittest.cc",
"tile_store_unittest.cc", "tile_store_unittest.cc",
"tile_utils_unittest.cc",
] ]
deps = [ deps = [
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
using TileStatsProto = query_tiles::proto::TileStats;
namespace query_tiles { namespace query_tiles {
namespace { namespace {
...@@ -108,6 +110,14 @@ void TileGroupToProto(TileGroup* group, TileGroupProto* proto) { ...@@ -108,6 +110,14 @@ void TileGroupToProto(TileGroup* group, TileGroupProto* proto) {
for (auto& tile : group->tiles) { for (auto& tile : group->tiles) {
TileToProto(tile.get(), proto->add_tiles()); TileToProto(tile.get(), proto->add_tiles());
} }
auto& map = *(proto->mutable_tile_stats());
for (auto& entry : group->tile_stats) {
TileStatsProto stats;
stats.set_score(entry.second.score);
stats.set_last_clicked_time_ms(
TimeToMilliseconds(entry.second.last_clicked_time));
map[entry.first] = stats;
}
} }
void TileGroupFromProto(TileGroupProto* proto, TileGroup* group) { void TileGroupFromProto(TileGroupProto* proto, TileGroup* group) {
...@@ -120,6 +130,11 @@ void TileGroupFromProto(TileGroupProto* proto, TileGroup* group) { ...@@ -120,6 +130,11 @@ void TileGroupFromProto(TileGroupProto* proto, TileGroup* group) {
TileFromProto(&entry_proto, child.get()); TileFromProto(&entry_proto, child.get());
group->tiles.emplace_back(std::move(child)); group->tiles.emplace_back(std::move(child));
} }
for (auto& entry : proto->tile_stats()) {
group->tile_stats[entry.first] =
TileStats(MillisecondsToTime(entry.second.last_clicked_time_ms()),
entry.second.score());
}
} }
void TileGroupFromResponse(const ResponseGroupProto& response, void TileGroupFromResponse(const ResponseGroupProto& response,
......
...@@ -20,6 +20,7 @@ void DeepCopyGroup(const TileGroup& input, TileGroup* output) { ...@@ -20,6 +20,7 @@ void DeepCopyGroup(const TileGroup& input, TileGroup* output) {
output->tiles.clear(); output->tiles.clear();
for (const auto& tile : input.tiles) for (const auto& tile : input.tiles)
output->tiles.emplace_back(std::make_unique<Tile>(*tile.get())); output->tiles.emplace_back(std::make_unique<Tile>(*tile.get()));
output->tile_stats = input.tile_stats;
} }
} // namespace } // namespace
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef COMPONENTS_QUERY_TILES_INTERNAL_TILE_GROUP_H_ #ifndef COMPONENTS_QUERY_TILES_INTERNAL_TILE_GROUP_H_
#define COMPONENTS_QUERY_TILES_INTERNAL_TILE_GROUP_H_ #define COMPONENTS_QUERY_TILES_INTERNAL_TILE_GROUP_H_
#include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
...@@ -40,6 +41,9 @@ struct TileGroup { ...@@ -40,6 +41,9 @@ struct TileGroup {
// Top level tiles. // Top level tiles.
std::vector<std::unique_ptr<Tile>> tiles; std::vector<std::unique_ptr<Tile>> tiles;
// Map from tile id to its stats.
std::map<std::string, TileStats> tile_stats;
// Print pretty formatted content in TileGroup struct. // Print pretty formatted content in TileGroup struct.
std::string DebugString(); std::string DebugString();
}; };
......
...@@ -17,10 +17,14 @@ ...@@ -17,10 +17,14 @@
#include "components/query_tiles/internal/tile_config.h" #include "components/query_tiles/internal/tile_config.h"
#include "components/query_tiles/internal/tile_iterator.h" #include "components/query_tiles/internal/tile_iterator.h"
#include "components/query_tiles/internal/tile_manager.h" #include "components/query_tiles/internal/tile_manager.h"
#include "components/query_tiles/internal/tile_utils.h"
namespace query_tiles { namespace query_tiles {
namespace { namespace {
// A special tile group for tile stats.
constexpr char kTileStatsGroup[] = "tile_stats";
class TileManagerImpl : public TileManager { class TileManagerImpl : public TileManager {
public: public:
TileManagerImpl(std::unique_ptr<TileStore> store, TileManagerImpl(std::unique_ptr<TileStore> store,
...@@ -137,6 +141,9 @@ class TileManagerImpl : public TileManager { ...@@ -137,6 +141,9 @@ class TileManagerImpl : public TileManager {
if (!group) if (!group)
continue; continue;
if (pair.first == kTileStatsGroup)
continue;
if (ValidateLocale(group) && !IsGroupExpired(group) && if (ValidateLocale(group) && !IsGroupExpired(group) &&
(group->last_updated_ts > last_updated_time)) { (group->last_updated_ts > last_updated_time)) {
last_updated_time = group->last_updated_ts; last_updated_time = group->last_updated_ts;
...@@ -152,6 +159,15 @@ class TileManagerImpl : public TileManager { ...@@ -152,6 +159,15 @@ class TileManagerImpl : public TileManager {
status = TileGroupStatus::kNoTiles; status = TileGroupStatus::kNoTiles;
} }
// Keep the stats group in memory for tile score calculation.
if (loaded_groups.find(kTileStatsGroup) != loaded_groups.end()) {
tile_stats_group_ = std::move(loaded_groups[kTileStatsGroup]);
// prevent the stats group from being deleted.
loaded_groups.erase(kTileStatsGroup);
if (tile_group_)
SortTiles(&tile_group_->tiles, &tile_stats_group_->tile_stats);
}
// Deletes other groups. // Deletes other groups.
for (const auto& group_to_delete : loaded_groups) for (const auto& group_to_delete : loaded_groups)
DeleteGroup(group_to_delete.first); DeleteGroup(group_to_delete.first);
...@@ -222,6 +238,11 @@ class TileManagerImpl : public TileManager { ...@@ -222,6 +238,11 @@ class TileManagerImpl : public TileManager {
// The tile group in-memory holder. // The tile group in-memory holder.
std::unique_ptr<TileGroup> tile_group_; std::unique_ptr<TileGroup> tile_group_;
// The tile group that contains stats for ranking all tiles.
// TODO(qinmin): Having a separate TileGroup just for ranking the tiles
// seems weird, probably do it through a separate store or use PrefService.
std::unique_ptr<TileGroup> tile_stats_group_;
// Clock object. // Clock object.
base::Clock* clock_; base::Clock* clock_;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <limits>
#include "components/query_tiles/internal/tile_utils.h"
namespace query_tiles {
namespace {
struct TileComparator {
explicit TileComparator(const std::map<std::string, TileStats>& tile_stats)
: tile_stats(tile_stats) {}
inline bool operator()(const std::unique_ptr<Tile>& a,
const std::unique_ptr<Tile>& b) {
auto iter_a = tile_stats.find(a->id);
auto iter_b = tile_stats.find(b->id);
return (iter_a != tile_stats.end() ? iter_a->second.score : 0) >
(iter_b != tile_stats.end() ? iter_b->second.score : 0);
}
std::map<std::string, TileStats> tile_stats;
};
} // namespace
void SortTiles(std::vector<std::unique_ptr<Tile>>* tiles,
std::map<std::string, TileStats>* tile_stats) {
if (!tiles || tiles->empty())
return;
// Some tiles do not have scores, so the first step is to calculate scores
// for them.
// To calculate scores for new tiles, ordering from the server response will
// be taken into consideration. As the server has already ordered tiles
// according to their importance.
// For example, if the first tile returned by server never appeared before, we
// should set its score to at least the 2nd tile. so that it can show up in
// the first place if no other tiles in the back have a higher score. For
// a new tile at position x, its score should be the minimum of its neighbors
// at position x-1 and x+1. For new tiles showing up at the end, their score
// will be set to 0.
// For example, if the tile scores are (new_tile, 0.5, 0.7), then the adjusted
// score will be (0.5, 0.5, 0.7). Simularly, (0.5, new_tile1, 0.7, new_tile2)
// will result in (0.5, 0.5, 0.7, 0).
double last_score = std::numeric_limits<double>::max();
size_t new_tile_index = 0;
base::Time now_time = base::Time::Now();
// Find any tiles that don't have scores, and add new entries for them.
for (size_t i = 0; i < tiles->size(); ++i) {
auto iter = tile_stats->find((*tiles)[i]->id);
// Find a new tile. Skip it for now, will add the entry when we found the
// first
if (iter == tile_stats->end())
continue;
// If the previous tiles are new tiles, fill them with a value that is
// minimum of their neighbors.
if (i > new_tile_index) {
double score = std::min(last_score, iter->second.score);
TileStats new_stats(now_time, score);
for (size_t j = new_tile_index; j < i; ++j)
tile_stats->emplace((*tiles)[j]->id, new_stats);
}
// Move |new_tile_index| to the next one that might not have
// a score.
new_tile_index = i + 1;
last_score = iter->second.score;
}
if (new_tile_index < tiles->size()) {
TileStats new_stats(now_time, 0);
for (size_t j = new_tile_index; j < tiles->size(); ++j)
tile_stats->emplace((*tiles)[j]->id, new_stats);
}
// Sort the tiles in descending order.
std::sort(tiles->begin(), tiles->end(), TileComparator(*tile_stats));
for (auto& tile : *tiles)
SortTiles(&tile->sub_tiles, tile_stats);
}
} // namespace query_tiles
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_QUERY_TILES_INTERNAL_TILE_UTILS_H_
#define COMPONENTS_QUERY_TILES_INTERNAL_TILE_UTILS_H_
#include <map>
#include <memory>
#include <vector>
#include "components/query_tiles/tile.h"
namespace query_tiles {
// Function to sort a vector of tiles based on their score in |tile_stats|. If
// a tile ID doesn't exists in |tile_stats|, a new entry will be created and
// a score will be calculated.
void SortTiles(std::vector<std::unique_ptr<Tile>>* tiles,
std::map<std::string, TileStats>* tile_stats);
} // namespace query_tiles
#endif // COMPONENTS_QUERY_TILES_INTERNAL_TILE_UTILS_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/query_tiles/internal/tile_utils.h"
#include "components/query_tiles/internal/tile_group.h"
#include "components/query_tiles/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace query_tiles {
namespace {
TEST(TileUtilsTest, Sort) {
TileGroup group;
test::ResetTestGroup(&group);
SortTiles(&group.tiles, &group.tile_stats);
EXPECT_EQ(group.tiles[0]->id, "guid-1-3");
EXPECT_EQ(group.tiles[1]->id, "guid-1-1");
EXPECT_EQ(group.tiles[2]->id, "guid-1-2");
EXPECT_EQ(group.tiles[1]->sub_tiles[0]->id, "guid-2-2");
EXPECT_EQ(group.tiles[1]->sub_tiles[1]->id, "guid-2-1");
EXPECT_EQ(group.tiles[0]->sub_tiles[0]->id, "guid-1-4");
EXPECT_EQ(group.tiles[1]->sub_tiles[1]->sub_tiles[0]->id, "guid-3-1");
}
TEST(TileUtilsTest, SortWithEmptytile_stats) {
TileGroup group;
test::ResetTestGroup(&group);
std::map<std::string, TileStats> tile_stats;
SortTiles(&group.tiles, &tile_stats);
EXPECT_EQ(group.tiles[0]->id, "guid-1-1");
EXPECT_EQ(group.tiles[1]->id, "guid-1-2");
EXPECT_EQ(group.tiles[2]->id, "guid-1-3");
EXPECT_EQ(group.tiles[0]->sub_tiles[0]->id, "guid-2-1");
EXPECT_EQ(group.tiles[0]->sub_tiles[1]->id, "guid-2-2");
}
// If new tiles are at the front, tile ordering should be kept after
// sort.
TEST(TileUtilsTest, SortWithNewTilesAtTheFront) {
TileGroup group;
test::ResetTestGroup(&group);
std::map<std::string, TileStats> tile_stats;
tile_stats["guid-1-3"] = TileStats(group.last_updated_ts, 0.7);
tile_stats["guid-1-4"] = TileStats(group.last_updated_ts, 0.4);
tile_stats["guid-2-2"] = TileStats(group.last_updated_ts, 0.6);
SortTiles(&group.tiles, &tile_stats);
EXPECT_EQ(group.tiles[0]->id, "guid-1-1");
EXPECT_EQ(group.tiles[1]->id, "guid-1-2");
EXPECT_EQ(group.tiles[2]->id, "guid-1-3");
EXPECT_EQ(group.tiles[0]->sub_tiles[0]->id, "guid-2-1");
EXPECT_EQ(group.tiles[0]->sub_tiles[1]->id, "guid-2-2");
EXPECT_EQ(tile_stats["guid-1-1"].score, 0.7);
EXPECT_EQ(tile_stats["guid-1-2"].score, 0.7);
EXPECT_EQ(tile_stats["guid-2-1"].score, 0.6);
}
// If new tiles are at the end, tile ordering should be kept after
// sort.
TEST(TileUtilsTest, SortWithNewTilesAtTheEnd) {
TileGroup group;
test::ResetTestGroup(&group);
std::map<std::string, TileStats> tile_stats;
tile_stats["guid-1-1"] = TileStats(group.last_updated_ts, 0.5);
tile_stats["guid-1-2"] = TileStats(group.last_updated_ts, 0.2);
tile_stats["guid-2-1"] = TileStats(group.last_updated_ts, 0.3);
SortTiles(&group.tiles, &tile_stats);
EXPECT_EQ(group.tiles[0]->id, "guid-1-1");
EXPECT_EQ(group.tiles[1]->id, "guid-1-2");
EXPECT_EQ(group.tiles[2]->id, "guid-1-3");
EXPECT_EQ(group.tiles[0]->sub_tiles[0]->id, "guid-2-1");
EXPECT_EQ(group.tiles[0]->sub_tiles[1]->id, "guid-2-2");
EXPECT_EQ(tile_stats["guid-1-3"].score, 0);
EXPECT_EQ(tile_stats["guid-2-2"].score, 0);
}
// Test the case that new tiles are in the middle.
TEST(TileUtilsTest, SortWithNewTilesInTheMiddle) {
TileGroup group;
test::ResetTestGroup(&group);
std::map<std::string, TileStats> tile_stats;
tile_stats["guid-1-1"] = TileStats(group.last_updated_ts, 0.5);
tile_stats["guid-1-3"] = TileStats(group.last_updated_ts, 0.7);
SortTiles(&group.tiles, &tile_stats);
EXPECT_EQ(group.tiles[0]->id, "guid-1-3");
EXPECT_EQ(group.tiles[1]->id, "guid-1-1");
EXPECT_EQ(group.tiles[2]->id, "guid-1-2");
EXPECT_EQ(tile_stats["guid-1-2"].score, 0.5);
}
} // namespace
} // namespace query_tiles
...@@ -40,8 +40,18 @@ message Tile { ...@@ -40,8 +40,18 @@ message Tile {
repeated string search_params = 7; repeated string search_params = 7;
} }
// Stats of the tile. Used for ranking tiles.
// Next tag: 3
message TileStats {
// Timestamp when the tile is last clicked, in ms.
int64 last_clicked_time_ms = 1;
// Score of the tile, used for ranking.
double score = 2;
}
// Data schema of a group of entries and its metadata. // Data schema of a group of entries and its metadata.
// Next tag: 5 // Next tag: 6
message TileGroup { message TileGroup {
// Unique id of each group. // Unique id of each group.
string id = 1; string id = 1;
...@@ -54,4 +64,7 @@ message TileGroup { ...@@ -54,4 +64,7 @@ message TileGroup {
// Top level tiles; // Top level tiles;
repeated Tile tiles = 4; repeated Tile tiles = 4;
// Map from tile id to its stats.
map<string, TileStats> tile_stats = 5;
} }
...@@ -56,6 +56,13 @@ void ResetTestGroup(TileGroup* group) { ...@@ -56,6 +56,13 @@ void ResetTestGroup(TileGroup* group) {
group->tiles.emplace_back(std::move(test_entry_1)); group->tiles.emplace_back(std::move(test_entry_1));
group->tiles.emplace_back(std::move(test_entry_2)); group->tiles.emplace_back(std::move(test_entry_2));
group->tiles.emplace_back(std::move(test_entry_3)); group->tiles.emplace_back(std::move(test_entry_3));
group->tile_stats["guid-1-1"] = TileStats(group->last_updated_ts, 0.5);
group->tile_stats["guid-1-2"] = TileStats(group->last_updated_ts, 0.2);
group->tile_stats["guid-1-3"] = TileStats(group->last_updated_ts, 0.7);
group->tile_stats["guid-1-4"] = TileStats(group->last_updated_ts, 0.4);
group->tile_stats["guid-2-1"] = TileStats(group->last_updated_ts, 0.3);
group->tile_stats["guid-2-2"] = TileStats(group->last_updated_ts, 0.6);
group->tile_stats["guid-3-1"] = TileStats(group->last_updated_ts, 0.5);
} }
bool AreTileGroupsIdentical(const TileGroup& lhs, const TileGroup& rhs) { bool AreTileGroupsIdentical(const TileGroup& lhs, const TileGroup& rhs) {
...@@ -72,7 +79,7 @@ bool AreTileGroupsIdentical(const TileGroup& lhs, const TileGroup& rhs) { ...@@ -72,7 +79,7 @@ bool AreTileGroupsIdentical(const TileGroup& lhs, const TileGroup& rhs) {
return false; return false;
} }
return true; return lhs.tile_stats == rhs.tile_stats;
} }
bool AreTilesIdentical(const Tile& lhs, const Tile& rhs) { bool AreTilesIdentical(const Tile& lhs, const Tile& rhs) {
......
...@@ -88,6 +88,19 @@ bool ImageMetadata::operator==(const ImageMetadata& other) const { ...@@ -88,6 +88,19 @@ bool ImageMetadata::operator==(const ImageMetadata& other) const {
return url == other.url; return url == other.url;
} }
TileStats::TileStats() = default;
TileStats::TileStats(base::Time last_clicked_time, double score)
: last_clicked_time(last_clicked_time), score(score) {}
TileStats::~TileStats() = default;
TileStats::TileStats(const TileStats& other) = default;
bool TileStats::operator==(const TileStats& other) const {
return last_clicked_time == other.last_clicked_time && score == other.score;
}
bool Tile::operator==(const Tile& other) const { bool Tile::operator==(const Tile& other) const {
return id == other.id && display_text == other.display_text && return id == other.id && display_text == other.display_text &&
query_text == other.query_text && query_text == other.query_text &&
......
...@@ -26,6 +26,21 @@ struct ImageMetadata { ...@@ -26,6 +26,21 @@ struct ImageMetadata {
GURL url; GURL url;
}; };
// Stats of a tile, used for ranking.
struct TileStats {
TileStats();
TileStats(base::Time last_clicked_time, double score);
~TileStats();
TileStats(const TileStats& other);
bool operator==(const TileStats& other) const;
// Last clicked timestamp.
base::Time last_clicked_time;
// Score of the tile, used for ranking.
double score;
};
// Represents the in memory structure of Tile. // Represents the in memory structure of Tile.
struct Tile { struct Tile {
Tile(); Tile();
......
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