Commit 0cc0a6c7 authored by rfevang@chromium.org's avatar rfevang@chromium.org

Support for different weight-functions for time-slicing.


BUG=
TEST=


Review URL: https://chromiumcodereview.appspot.com/10060003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@137229 0039d316-1c4b-4281-b951-d872f2087c98
parent 8c8f79c9
...@@ -1282,7 +1282,7 @@ void HistoryBackend::QueryHistoryBasic(URLDatabase* url_db, ...@@ -1282,7 +1282,7 @@ void HistoryBackend::QueryHistoryBasic(URLDatabase* url_db,
// First get all visits. // First get all visits.
VisitVector visits; VisitVector visits;
visit_db->GetVisibleVisitsInRange(options.begin_time, options.end_time, visit_db->GetVisibleVisitsInRange(options.begin_time, options.end_time,
options.max_count, &visits); options.max_count, &visits, true);
DCHECK(options.max_count == 0 || DCHECK(options.max_count == 0 ||
static_cast<int>(visits.size()) <= options.max_count); static_cast<int>(visits.size()) <= options.max_count);
...@@ -1482,8 +1482,6 @@ void HistoryBackend::QueryFilteredURLs( ...@@ -1482,8 +1482,6 @@ void HistoryBackend::QueryFilteredURLs(
} }
std::map<URLID, double> score_map; std::map<URLID, double> score_map;
const double kLn2 = 0.6931471805599453;
base::Time now = base::Time::Now();
for (size_t i = 0; i < visits.size(); ++i) { for (size_t i = 0; i < visits.size(); ++i) {
URLID segment_id = visits[i].segment_id; URLID segment_id = visits[i].segment_id;
for (VisitID visit_id = visits[i].visit_id; !segment_id && visit_id;) { for (VisitID visit_id = visits[i].visit_id; !segment_id && visit_id;) {
...@@ -1504,23 +1502,7 @@ void HistoryBackend::QueryFilteredURLs( ...@@ -1504,23 +1502,7 @@ void HistoryBackend::QueryFilteredURLs(
} }
if (!segment_id) if (!segment_id)
continue; continue;
double score = 0.0; double score = filter.GetVisitScore(visits[i]);
switch (filter.sorting_order()) {
case VisitFilter::ORDER_BY_RECENCY: {
// Decay score by half each week.
base::TimeDelta time_passed = now - visits[i].visit_time;
// Clamp to 0 in case time jumps backwards (e.g. due to DST).
double decay_exponent = std::max(0.0, kLn2 * static_cast<double>(
time_passed.InMicroseconds()) / base::Time::kMicrosecondsPerWeek);
score = 1.0 / exp(decay_exponent);
} break;
case VisitFilter::ORDER_BY_VISIT_COUNT:
score = 1.0; // Every visit counts the same.
break;
case VisitFilter::ORDER_BY_DURATION_SPENT:
NOTREACHED() << "Not implemented!";
break;
}
std::map<URLID, double>::iterator it = score_map.find(visits[i].segment_id); std::map<URLID, double>::iterator it = score_map.find(visits[i].segment_id);
if (it == score_map.end()) if (it == score_map.end())
......
...@@ -613,7 +613,7 @@ TEST_F(HistoryBackendTest, KeywordGenerated) { ...@@ -613,7 +613,7 @@ TEST_F(HistoryBackendTest, KeywordGenerated) {
// But no visible visits. // But no visible visits.
visits.clear(); visits.clear();
backend_->db()->GetVisibleVisitsInRange(base::Time(), base::Time(), 1, backend_->db()->GetVisibleVisitsInRange(base::Time(), base::Time(), 1,
&visits); &visits, true);
EXPECT_TRUE(visits.empty()); EXPECT_TRUE(visits.empty());
// Expire the visits. // Expire the visits.
...@@ -1258,6 +1258,7 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1258,6 +1258,7 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
Time tested_time = Time::Now().LocalMidnight() + Time tested_time = Time::Now().LocalMidnight() +
base::TimeDelta::FromHours(4); base::TimeDelta::FromHours(4);
base::TimeDelta half_an_hour = base::TimeDelta::FromMinutes(30); base::TimeDelta half_an_hour = base::TimeDelta::FromMinutes(30);
base::TimeDelta one_hour = base::TimeDelta::FromHours(1);
base::TimeDelta one_day = base::TimeDelta::FromDays(1); base::TimeDelta one_day = base::TimeDelta::FromDays(1);
const content::PageTransition kTypedTransition = const content::PageTransition kTypedTransition =
...@@ -1320,8 +1321,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1320,8 +1321,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
VisitFilter filter; VisitFilter filter;
// Time limit is |tested_time| +/- 45 min. // Time limit is |tested_time| +/- 45 min.
base::TimeDelta three_quarters_of_an_hour = base::TimeDelta::FromMinutes(45); base::TimeDelta three_quarters_of_an_hour = base::TimeDelta::FromMinutes(45);
filter.SetTimeInRangeFilter(tested_time - three_quarters_of_an_hour, filter.SetFilterTime(tested_time);
tested_time + three_quarters_of_an_hour); filter.SetFilterWidth(three_quarters_of_an_hour);
backend_->QueryFilteredURLs(request1, 100, filter); backend_->QueryFilteredURLs(request1, 100, filter);
ASSERT_EQ(4U, get_filtered_list().size()); ASSERT_EQ(4U, get_filtered_list().size());
...@@ -1339,8 +1340,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1339,8 +1340,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
base::Unretained(static_cast<HistoryBackendTest*>(this)))); base::Unretained(static_cast<HistoryBackendTest*>(this))));
cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>( cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
request2); request2);
filter.SetTimeInRangeFilter(tested_time, filter.SetFilterTime(tested_time + one_hour);
tested_time + base::TimeDelta::FromHours(2)); filter.SetFilterWidth(one_hour);
backend_->QueryFilteredURLs(request2, 100, filter); backend_->QueryFilteredURLs(request2, 100, filter);
ASSERT_EQ(3U, get_filtered_list().size()); ASSERT_EQ(3U, get_filtered_list().size());
...@@ -1355,8 +1356,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1355,8 +1356,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
base::Unretained(static_cast<HistoryBackendTest*>(this)))); base::Unretained(static_cast<HistoryBackendTest*>(this))));
cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>( cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
request3); request3);
filter.SetTimeInRangeFilter(tested_time - base::TimeDelta::FromHours(2), filter.SetFilterTime(tested_time - one_hour);
tested_time); filter.SetFilterWidth(one_hour);
backend_->QueryFilteredURLs(request3, 100, filter); backend_->QueryFilteredURLs(request3, 100, filter);
ASSERT_EQ(3U, get_filtered_list().size()); ASSERT_EQ(3U, get_filtered_list().size());
...@@ -1376,8 +1377,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1376,8 +1377,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
base::Unretained(static_cast<HistoryBackendTest*>(this)))); base::Unretained(static_cast<HistoryBackendTest*>(this))));
cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>( cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
request4); request4);
filter.SetDayOfTheWeekFilter(static_cast<int>(exploded_time.day_of_week), filter.SetFilterTime(tested_time);
tested_time); filter.SetDayOfTheWeekFilter(static_cast<int>(exploded_time.day_of_week));
backend_->QueryFilteredURLs(request4, 100, filter); backend_->QueryFilteredURLs(request4, 100, filter);
ASSERT_EQ(2U, get_filtered_list().size()); ASSERT_EQ(2U, get_filtered_list().size());
...@@ -1392,8 +1393,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) { ...@@ -1392,8 +1393,8 @@ TEST_F(HistoryBackendTest, QueryFilteredURLs) {
base::Unretained(static_cast<HistoryBackendTest*>(this)))); base::Unretained(static_cast<HistoryBackendTest*>(this))));
cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>( cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
request5); request5);
filter.SetTimeInRangeFilter(tested_time - base::TimeDelta::FromHours(1), filter.SetFilterTime(tested_time - base::TimeDelta::FromMinutes(40));
tested_time - base::TimeDelta::FromMinutes(20)); filter.SetFilterWidth(base::TimeDelta::FromMinutes(20));
backend_->QueryFilteredURLs(request5, 100, filter); backend_->QueryFilteredURLs(request5, 100, filter);
ASSERT_EQ(1U, get_filtered_list().size()); ASSERT_EQ(1U, get_filtered_list().size());
......
...@@ -301,7 +301,8 @@ bool VisitDatabase::GetVisitsInRangeForTransition( ...@@ -301,7 +301,8 @@ bool VisitDatabase::GetVisitsInRangeForTransition(
void VisitDatabase::GetVisibleVisitsInRange(base::Time begin_time, void VisitDatabase::GetVisibleVisitsInRange(base::Time begin_time,
base::Time end_time, base::Time end_time,
int max_count, int max_count,
VisitVector* visits) { VisitVector* visits,
bool unique) {
visits->clear(); visits->clear();
// The visit_time values can be duplicated in a redirect chain, so we sort // The visit_time values can be duplicated in a redirect chain, so we sort
// by id too, to ensure a consistent ordering just in case. // by id too, to ensure a consistent ordering just in case.
...@@ -329,10 +330,12 @@ void VisitDatabase::GetVisibleVisitsInRange(base::Time begin_time, ...@@ -329,10 +330,12 @@ void VisitDatabase::GetVisibleVisitsInRange(base::Time begin_time,
while (statement.Step()) { while (statement.Step()) {
VisitRow visit; VisitRow visit;
FillVisitRow(statement, &visit); FillVisitRow(statement, &visit);
// Make sure the URL this visit corresponds to is unique. if (unique) {
if (found_urls.find(visit.url_id) != found_urls.end()) // Make sure the URL this visit corresponds to is unique.
continue; if (found_urls.find(visit.url_id) != found_urls.end())
found_urls.insert(visit.url_id); continue;
found_urls.insert(visit.url_id);
}
visits->push_back(visit); visits->push_back(visit);
if (max_count > 0 && static_cast<int>(visits->size()) >= max_count) if (max_count > 0 && static_cast<int>(visits->size()) >= max_count)
...@@ -349,7 +352,7 @@ void VisitDatabase::GetVisibleVisitsDuringTimes(const VisitFilter& time_filter, ...@@ -349,7 +352,7 @@ void VisitDatabase::GetVisibleVisitsDuringTimes(const VisitFilter& time_filter,
for (VisitFilter::TimeVector::const_iterator it = time_filter.times().begin(); for (VisitFilter::TimeVector::const_iterator it = time_filter.times().begin();
it != time_filter.times().end(); ++it) { it != time_filter.times().end(); ++it) {
VisitVector v; VisitVector v;
GetVisibleVisitsInRange(it->first, it->second, max_results, &v); GetVisibleVisitsInRange(it->first, it->second, max_results, &v, false);
size_t take_only = 0; size_t take_only = 0;
if (max_results && if (max_results &&
static_cast<int>(visits->size() + v.size()) > max_results) { static_cast<int>(visits->size() + v.size()) > max_results) {
......
...@@ -98,11 +98,12 @@ class VisitDatabase { ...@@ -98,11 +98,12 @@ class VisitDatabase {
// that, the most recent |max_count| will be returned. If 0, all visits in the // that, the most recent |max_count| will be returned. If 0, all visits in the
// range will be computed. // range will be computed.
// //
// Only one visit for each URL will be returned, and it will be the most // If |unique| is set, only one visit for each URL will be returned, and it
// recent one in the time range. // will be the most recent one in the time range.
void GetVisibleVisitsInRange(base::Time begin_time, base::Time end_time, void GetVisibleVisitsInRange(base::Time begin_time, base::Time end_time,
int max_count, int max_count,
VisitVector* visits); VisitVector* visits,
bool unique);
// Fills all visits in the given time ranges into the given vector that should // Fills all visits in the given time ranges into the given vector that should
// be user-visible, which excludes things like redirects and subframes. The // be user-visible, which excludes things like redirects and subframes. The
...@@ -111,9 +112,6 @@ class VisitDatabase { ...@@ -111,9 +112,6 @@ class VisitDatabase {
// Up to |max_count| visits will be returned. If there are more visits than // Up to |max_count| visits will be returned. If there are more visits than
// that, the most recent |max_count| will be returned. If 0, all visits in the // that, the most recent |max_count| will be returned. If 0, all visits in the
// range will be computed. // range will be computed.
//
// Only one visit for each URL will be returned, and it will be the most
// recent one in the time range.
void GetVisibleVisitsDuringTimes(const VisitFilter& time_filter, void GetVisibleVisitsDuringTimes(const VisitFilter& time_filter,
int max_count, int max_count,
VisitVector* visits); VisitVector* visits);
......
// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
...@@ -212,7 +212,7 @@ TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) { ...@@ -212,7 +212,7 @@ TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
// Query the visits for all time, we should not get the first (duplicate of // Query the visits for all time, we should not get the first (duplicate of
// the second) or the redirect or subframe visits. // the second) or the redirect or subframe visits.
VisitVector results; VisitVector results;
GetVisibleVisitsInRange(Time(), Time(), 0, &results); GetVisibleVisitsInRange(Time(), Time(), 0, &results, true);
ASSERT_EQ(static_cast<size_t>(2), results.size()); ASSERT_EQ(static_cast<size_t>(2), results.size());
EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4) && EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4) &&
IsVisitInfoEqual(results[1], visit_info2)); IsVisitInfoEqual(results[1], visit_info2));
...@@ -220,12 +220,12 @@ TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) { ...@@ -220,12 +220,12 @@ TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
// Query a time range and make sure beginning is inclusive and ending is // Query a time range and make sure beginning is inclusive and ending is
// exclusive. // exclusive.
GetVisibleVisitsInRange(visit_info2.visit_time, visit_info4.visit_time, 0, GetVisibleVisitsInRange(visit_info2.visit_time, visit_info4.visit_time, 0,
&results); &results, true);
ASSERT_EQ(static_cast<size_t>(1), results.size()); ASSERT_EQ(static_cast<size_t>(1), results.size());
EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info2)); EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info2));
// Query for a max count and make sure we get only that number. // Query for a max count and make sure we get only that number.
GetVisibleVisitsInRange(Time(), Time(), 1, &results); GetVisibleVisitsInRange(Time(), Time(), 1, &results, true);
ASSERT_EQ(static_cast<size_t>(1), results.size()); ASSERT_EQ(static_cast<size_t>(1), results.size());
EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4)); EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4));
} }
......
...@@ -4,13 +4,18 @@ ...@@ -4,13 +4,18 @@
#include "chrome/browser/history/visit_filter.h" #include "chrome/browser/history/visit_filter.h"
#include <math.h>
#include <algorithm> #include <algorithm>
#include "base/logging.h" #include "base/logging.h"
#include "base/time.h" #include "base/time.h"
#include "chrome/browser/history/history_types.h"
namespace history { namespace history {
const double kLn2 = 0.6931471805599453;
VisitFilter::VisitFilter() VisitFilter::VisitFilter()
: day_(DAY_UNDEFINED), : day_(DAY_UNDEFINED),
max_results_(0), max_results_(0),
...@@ -20,54 +25,58 @@ VisitFilter::VisitFilter() ...@@ -20,54 +25,58 @@ VisitFilter::VisitFilter()
VisitFilter::~VisitFilter() { VisitFilter::~VisitFilter() {
} }
void VisitFilter::SetTimeInRangeFilter(base::Time begin_time_of_the_day, void VisitFilter::SetFilterTime(const base::Time& filter_time) {
base::Time end_time_of_the_day) { filter_time_ = filter_time;
DCHECK(!begin_time_of_the_day.is_null());
DCHECK(!end_time_of_the_day.is_null());
if ((end_time_of_the_day < begin_time_of_the_day) ||
((end_time_of_the_day - begin_time_of_the_day) >
base::TimeDelta::FromDays(1))) {
begin_time_of_the_day_ = base::Time();
end_time_of_the_day_ = base::Time();
} else {
begin_time_of_the_day_ = begin_time_of_the_day;
end_time_of_the_day_ = end_time_of_the_day;
}
UpdateTimeVector(); UpdateTimeVector();
} }
void VisitFilter::SetDayOfTheWeekFilter(int day, base::Time week) { void VisitFilter::SetFilterWidth(const base::TimeDelta& filter_width) {
filter_width_ = filter_width;
UpdateTimeVector();
}
void VisitFilter::SetDayOfTheWeekFilter(int day) {
day_ = day; day_ = day;
week_ = week;
UpdateTimeVector(); UpdateTimeVector();
} }
void VisitFilter::SetDayTypeFilter(bool workday, base::Time week) { void VisitFilter::SetDayTypeFilter(bool workday) {
day_ = workday ? WORKDAY : HOLIDAY; day_ = workday ? WORKDAY : HOLIDAY;
week_ = week;
UpdateTimeVector(); UpdateTimeVector();
} }
void VisitFilter::ClearFilters() { void VisitFilter::ClearFilters() {
begin_time_of_the_day_ = base::Time(); filter_time_ = base::Time();
end_time_of_the_day_ = base::Time(); filter_width_ = base::TimeDelta::FromHours(0);
day_ = DAY_UNDEFINED; day_ = DAY_UNDEFINED;
UpdateTimeVector(); UpdateTimeVector();
} }
bool VisitFilter::UpdateTimeVector() { bool VisitFilter::UpdateTimeVector() {
TimeVector times_of_the_day;
if (!begin_time_of_the_day_.is_null()) {
GetTimesInRange(begin_time_of_the_day_, end_time_of_the_day_,
max_results_, &times_of_the_day);
}
TimeVector days_of_the_week; TimeVector days_of_the_week;
if (day_ >= 0 && day_ <= 6) { if (day_ >= 0 && day_ <= 6) {
GetTimesOnTheDayOfTheWeek(day_, week_, max_results_, &days_of_the_week); GetTimesOnTheDayOfTheWeek(day_, filter_time_, max_results_,
&days_of_the_week);
} else if (day_ == WORKDAY || day_ == HOLIDAY) { } else if (day_ == WORKDAY || day_ == HOLIDAY) {
GetTimesOnTheSameDayType( GetTimesOnTheSameDayType(
(day_ == WORKDAY), week_, max_results_, &days_of_the_week); (day_ == WORKDAY), filter_time_, max_results_, &days_of_the_week);
}
TimeVector times_of_the_day;
if (filter_width_ != base::TimeDelta::FromSeconds(0)) {
if (sorting_order_ == ORDER_BY_TIME_GAUSSIAN) {
// Limit queries to 5 standard deviations.
GetTimesInRange(filter_time_ - 5 * filter_width_,
filter_time_ + 5 * filter_width_,
max_results_, &times_of_the_day);
} else {
GetTimesInRange(filter_time_ - filter_width_,
filter_time_ + filter_width_,
max_results_, &times_of_the_day);
}
} }
if (times_of_the_day.empty()) { if (times_of_the_day.empty()) {
if (days_of_the_week.empty()) if (days_of_the_week.empty())
times_.clear(); times_.clear();
...@@ -79,6 +88,7 @@ bool VisitFilter::UpdateTimeVector() { ...@@ -79,6 +88,7 @@ bool VisitFilter::UpdateTimeVector() {
else else
IntersectTimeVectors(times_of_the_day, days_of_the_week, &times_); IntersectTimeVectors(times_of_the_day, days_of_the_week, &times_);
} }
return !times_.empty(); return !times_.empty();
} }
...@@ -95,6 +105,16 @@ void VisitFilter::GetTimesInRange(base::Time begin_time_of_the_day, ...@@ -95,6 +105,16 @@ void VisitFilter::GetTimesInRange(base::Time begin_time_of_the_day,
if (!max_results) if (!max_results)
max_results = kMaxReturnedResults; max_results = kMaxReturnedResults;
// If range is more than 24 hours, return a contiguous interval covering
// |max_results| days.
base::TimeDelta one_day = base::TimeDelta::FromDays(1);
if (end_time_of_the_day - begin_time_of_the_day >= one_day) {
times->push_back(
std::make_pair(begin_time_of_the_day - one_day * (max_results - 1),
end_time_of_the_day));
return;
}
for (size_t i = 0; i < max_results; ++i) { for (size_t i = 0; i < max_results; ++i) {
times->push_back( times->push_back(
std::make_pair(begin_time_of_the_day - base::TimeDelta::FromDays(i), std::make_pair(begin_time_of_the_day - base::TimeDelta::FromDays(i),
...@@ -102,6 +122,70 @@ void VisitFilter::GetTimesInRange(base::Time begin_time_of_the_day, ...@@ -102,6 +122,70 @@ void VisitFilter::GetTimesInRange(base::Time begin_time_of_the_day,
} }
} }
double VisitFilter::GetVisitScore(const VisitRow& visit) const {
// Decay score by half each week.
base::TimeDelta time_passed = filter_time_ - visit.visit_time;
// Clamp to 0 in case time jumps backwards (e.g. due to DST).
double decay_exponent = std::max(0.0, kLn2 * static_cast<double>(
time_passed.InMicroseconds()) / base::Time::kMicrosecondsPerWeek);
double staleness = 1.0 / exp(decay_exponent);
double score = 0;
switch (sorting_order()) {
case ORDER_BY_RECENCY:
score = 1.0; // Let the staleness factor take care of it.
break;
case ORDER_BY_VISIT_COUNT:
score = 1.0; // Every visit counts the same.
staleness = 1.0; // No decay on this one.
break;
case ORDER_BY_TIME_GAUSSIAN: {
double offset =
GetTimeOfDayDifference(filter_time_,
visit.visit_time).InMicroseconds();
double sd = filter_width_.InMicroseconds();
// Calculate score using the normal distribution density function.
score = exp(-(offset * offset) / (2 * sd * sd));
break;
}
case ORDER_BY_TIME_LINEAR: {
base::TimeDelta offset = GetTimeOfDayDifference(filter_time_,
visit.visit_time);
if (offset > filter_width_) {
score = 0;
} else {
score = 1 - offset.InMicroseconds() / static_cast<double>(
filter_width_.InMicroseconds());
}
break;
}
case ORDER_BY_DURATION_SPENT:
default:
NOTREACHED() << "Not implemented!";
}
return staleness * score;
}
base::TimeDelta
VisitFilter::GetTimeOfDayDifference(base::Time t1, base::Time t2) {
base::TimeDelta time_of_day1 = t1 - t1.LocalMidnight();
base::TimeDelta time_of_day2 = t2 - t2.LocalMidnight();
base::TimeDelta difference;
if (time_of_day1 < time_of_day2)
difference = time_of_day2 - time_of_day1;
else
difference = time_of_day1 - time_of_day2;
// If the difference is more than 12 hours, we'll get closer by 'wrapping'
// around the day barrier.
if (difference > base::TimeDelta::FromHours(12))
difference = base::TimeDelta::FromHours(24) - difference;
return difference;
}
// static // static
void VisitFilter::GetTimesOnTheDayOfTheWeek(int day, void VisitFilter::GetTimesOnTheDayOfTheWeek(int day,
base::Time week, base::Time week,
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
namespace history { namespace history {
class VisitRow;
// Helper class for creation of filters for VisitDatabase that is used to filter // Helper class for creation of filters for VisitDatabase that is used to filter
// out visits by time of the day, day of the week, workdays, holidays, duration // out visits by time of the day, day of the week, workdays, holidays, duration
// of the visit, location and the combinations of that. // of the visit, location and the combinations of that.
...@@ -42,36 +44,31 @@ class VisitFilter { ...@@ -42,36 +44,31 @@ class VisitFilter {
times_.resize(max_results_); times_.resize(max_results_);
} }
// Sets time in range during the day, for example, the following code would // Sets the time that should be used as a basis for the filter. Normally this
// produce time vector with following values: 1/19/2005 9:07:06PM-9:27:06PM, // is the time that a query is made.
// 1/18/2005 9:07:06PM-9:27:06PM, 1/17/2005 9:07:06PM-9:27:06PM, etc. void SetFilterTime(const base::Time& filter_time);
// base::Time::Exploded et = { 2005, 1, 0, 19, 21, 17, 6, 0 };
// base::Time t(base::Time::FromLocalExploded(et)); // Sets the amount of time around the filter time to take into account. This
// base::TimeDelta ten_minutes(base::TimeDelta::FromMinutes(10)); // only applies to the filter time's time-of-day, restrictions on how long
// VisitFilter f; // back in time to look should be controlled by changing |max_results|.
// f.SetTimeInRangeFilter(t - ten_minutes, t + ten_minutes); //
// This filter could be applied simultaneously with day filters. // How the filter width is used depends on the sorting order. For
void SetTimeInRangeFilter(base::Time begin_time_of_the_day, // |ORDER_BY_TIME_LINEAR| it is the distance to the cutoff point, while for
base::Time end_time_of_the_day); // |ORDER_BY_TIME_GAUSSIAN| it is the standard deviation.
void SetFilterWidth(const base::TimeDelta& filter_width);
// The following two filters are exclusive - setting one, clears the other // The following two filters are exclusive - setting one, clears the other
// one. But both of them could be used with SetTimeInRangeFilter(). // one.
// Sets time in range for the day of the week staring with the day on the // Sets the filter to use only visits that happened on the specified day of
// |week|. The intervals counted from midnight to midnight. // the week.
// |day| - day of the week: 0 - sunday, 1 - monday, etc. // |day| - day of the week: 0 - sunday, 1 - monday, etc.
// |week| - the week relative to which everything is calculated. If |week| is void SetDayOfTheWeekFilter(int day);
// unset, the current week is used.
void SetDayOfTheWeekFilter(int day, base::Time week);
// Sets time in range for the holidays or workdays of the week staring with // Sets the filter to use only visits that happened on a holiday/workday.
// |week|. The intervals counted from midnight of the first workday/holiday
// to midnight of the last workday/holiday.
// |workday| - if true means Monday-Friday, if false means Saturday-Sunday. // |workday| - if true means Monday-Friday, if false means Saturday-Sunday.
// TODO(georgey) - internationalize it. // TODO(georgey) - internationalize it.
// |week| - the week relative to which everything is calculated. If |week| is void SetDayTypeFilter(bool workday);
// unset, the current week is used.
void SetDayTypeFilter(bool workday, base::Time week);
// Sorting order that results after applying this filter are sorted by. // Sorting order that results after applying this filter are sorted by.
enum SortingOrder { enum SortingOrder {
...@@ -79,10 +76,21 @@ class VisitFilter { ...@@ -79,10 +76,21 @@ class VisitFilter {
ORDER_BY_VISIT_COUNT, // Most visited are listed first. ORDER_BY_VISIT_COUNT, // Most visited are listed first.
ORDER_BY_DURATION_SPENT, // The sites that user spents more time in are ORDER_BY_DURATION_SPENT, // The sites that user spents more time in are
// sorted first. // sorted first.
ORDER_BY_TIME_GAUSSIAN, // Visits that happened closer to the filter time's
// time-of-day are scored higher. The dropoff in
// score follows a normal distribution curve with
// the filter width as the standard deviation.
ORDER_BY_TIME_LINEAR, // Visits that happened closer to the filter time's
// time-of-day are score higher. The dropoff in score
// is a linear function, with filter width being the
// point where a visit does not count at all anymore.
}; };
double GetVisitScore(const VisitRow& visit) const;
void set_sorting_order(SortingOrder order) { void set_sorting_order(SortingOrder order) {
sorting_order_ = order; sorting_order_ = order;
UpdateTimeVector();
} }
SortingOrder sorting_order() const { SortingOrder sorting_order() const {
...@@ -136,15 +144,18 @@ class VisitFilter { ...@@ -136,15 +144,18 @@ class VisitFilter {
const TimeVector& vector2, const TimeVector& vector2,
TimeVector* result); TimeVector* result);
base::Time begin_time_of_the_day_; // Returns the time-of-day difference between the two times. The result will
base::Time end_time_of_the_day_; // always represent a value between 0 and 12 hours inclusive.
static base::TimeDelta GetTimeOfDayDifference(base::Time t1, base::Time t2);
base::Time filter_time_;
base::TimeDelta filter_width_;
enum { enum {
DAY_UNDEFINED = -1, DAY_UNDEFINED = -1,
WORKDAY = 7, WORKDAY = 7,
HOLIDAY = 8, HOLIDAY = 8,
}; };
int day_; int day_;
base::Time week_;
TimeVector times_; TimeVector times_;
size_t max_results_; size_t max_results_;
SortingOrder sorting_order_; SortingOrder sorting_order_;
......
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
#include "chrome/browser/history/visit_filter.h" #include "chrome/browser/history/visit_filter.h"
#include <math.h>
#include "base/logging.h" #include "base/logging.h"
#include "base/time.h" #include "base/time.h"
#include "chrome/browser/history/history_types.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace { namespace {
...@@ -43,7 +46,8 @@ TEST_F(VisitFilterTest, CheckFilters) { ...@@ -43,7 +46,8 @@ TEST_F(VisitFilterTest, CheckFilters) {
base::TimeDelta two_hours(base::TimeDelta::FromHours(2)); base::TimeDelta two_hours(base::TimeDelta::FromHours(2));
VisitFilter f; VisitFilter f;
f.set_max_results(21U); f.set_max_results(21U);
f.SetTimeInRangeFilter(t - two_hours, t + two_hours); f.SetFilterTime(t);
f.SetFilterWidth(two_hours);
EXPECT_EQ(21U, f.times().size()); EXPECT_EQ(21U, f.times().size());
for (size_t i = 0; i < f.times().size(); ++i) { for (size_t i = 0; i < f.times().size(); ++i) {
base::Time t_interval(t); base::Time t_interval(t);
...@@ -55,7 +59,7 @@ TEST_F(VisitFilterTest, CheckFilters) { ...@@ -55,7 +59,7 @@ TEST_F(VisitFilterTest, CheckFilters) {
} }
base::Time::Exploded et; base::Time::Exploded et;
t.LocalExplode(&et); t.LocalExplode(&et);
f.SetDayOfTheWeekFilter(et.day_of_week, t); f.SetDayOfTheWeekFilter(et.day_of_week);
// 3 weeks in 21 days. // 3 weeks in 21 days.
ASSERT_EQ(3U, f.times().size()); ASSERT_EQ(3U, f.times().size());
for (size_t i = 1; i < f.times().size(); ++i) { for (size_t i = 1; i < f.times().size(); ++i) {
...@@ -235,4 +239,74 @@ TEST_F(VisitFilterTest, IntersectTimeVectors) { ...@@ -235,4 +239,74 @@ TEST_F(VisitFilterTest, IntersectTimeVectors) {
EXPECT_TRUE(result.empty()); EXPECT_TRUE(result.empty());
} }
TEST_F(VisitFilterTest, GetVisitScore) {
base::Time filter_time = GetClosestMidday();
VisitFilter filter;
VisitRow visit;
filter.set_sorting_order(VisitFilter::ORDER_BY_RECENCY);
filter.SetFilterTime(filter_time);
filter.SetFilterWidth(base::TimeDelta::FromHours(1));
double one_week_one_hour_staleness = pow(2, -(24.0 * 7.0 + 1.0) /
(24.0 * 7.0));
// No decay on current visit.
visit.visit_time = filter_time;
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
// Half score after a week.
visit.visit_time = filter_time - base::TimeDelta::FromDays(7);
EXPECT_DOUBLE_EQ(0.5, filter.GetVisitScore(visit));
// Future visits should be treated as current.
visit.visit_time = filter_time + base::TimeDelta::FromDays(1);
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
filter.set_sorting_order(VisitFilter::ORDER_BY_VISIT_COUNT);
// Every visit should score 1 with this filter.
visit.visit_time = filter_time;
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
visit.visit_time = filter_time - base::TimeDelta::FromDays(7);
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
visit.visit_time = filter_time + base::TimeDelta::FromDays(7);
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
filter.set_sorting_order(VisitFilter::ORDER_BY_TIME_LINEAR);
visit.visit_time = filter_time;
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
// Half the filter width forward in time should get half the score for the
// time difference, but no staleness decay.
visit.visit_time = filter_time + base::TimeDelta::FromMinutes(30);
EXPECT_DOUBLE_EQ(0.5, filter.GetVisitScore(visit));
// One week back in time gets full time difference score, but a staleness
// factor of 0.5
visit.visit_time = filter_time - base::TimeDelta::FromDays(7);
EXPECT_DOUBLE_EQ(0.5, filter.GetVisitScore(visit));
// One week plus half a filter width should have it's score halved before
// the staleness factor.
filter.SetFilterWidth(base::TimeDelta::FromHours(2));
visit.visit_time = filter_time - base::TimeDelta::FromDays(7) -
base::TimeDelta::FromHours(1);
EXPECT_DOUBLE_EQ(0.5 * one_week_one_hour_staleness,
filter.GetVisitScore(visit));
filter.SetFilterWidth(base::TimeDelta::FromHours(1));
filter.set_sorting_order(VisitFilter::ORDER_BY_TIME_GAUSSIAN);
visit.visit_time = filter_time;
EXPECT_DOUBLE_EQ(1.0, filter.GetVisitScore(visit));
// Going forward in time to test the normal distribution function.
visit.visit_time = filter_time + base::TimeDelta::FromHours(1);
EXPECT_DOUBLE_EQ(exp(-0.5), filter.GetVisitScore(visit));
visit.visit_time = filter_time + base::TimeDelta::FromMinutes(30);
EXPECT_DOUBLE_EQ(exp(-0.125), filter.GetVisitScore(visit));
// One week back in time gets full time difference score, but a staleness
// factor of 0.5
visit.visit_time = filter_time - base::TimeDelta::FromDays(7);
EXPECT_DOUBLE_EQ(0.5, filter.GetVisitScore(visit));
// One standard deviation of decay, plus the staleness factor.
visit.visit_time = filter_time - base::TimeDelta::FromDays(7) -
base::TimeDelta::FromHours(1);
EXPECT_DOUBLE_EQ(exp(-0.5) * one_week_one_hour_staleness,
filter.GetVisitScore(visit));
}
} // namespace history } // namespace history
...@@ -6,13 +6,17 @@ ...@@ -6,13 +6,17 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/values.h" #include "base/values.h"
#include "base/string_number_conversions.h"
#include "chrome/browser/history/top_sites.h" #include "chrome/browser/history/top_sites.h"
#include "chrome/browser/history/visit_filter.h" #include "chrome/browser/history/visit_filter.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/ntp/new_tab_ui.h" #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
#include "chrome/browser/ui/webui/ntp/suggestions_combiner.h" #include "chrome/browser/ui/webui/ntp/suggestions_combiner.h"
#include "chrome/common/chrome_switches.h"
namespace { namespace {
...@@ -56,10 +60,10 @@ void SuggestionsSourceTopSites::FetchItems(Profile* profile) { ...@@ -56,10 +60,10 @@ void SuggestionsSourceTopSites::FetchItems(Profile* profile) {
// |history| may be null during unit tests. // |history| may be null during unit tests.
if (history) { if (history) {
history::VisitFilter time_filter; history::VisitFilter time_filter;
base::TimeDelta half_an_hour = time_filter.SetFilterTime(base::Time::Now());
base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerHour / 2); time_filter.SetFilterWidth(GetFilterWidth());
base::Time now = base::Time::Now(); time_filter.set_sorting_order(GetSortingOrder());
time_filter.SetTimeInRangeFilter(now - half_an_hour, now + half_an_hour);
history->QueryFilteredURLs(0, time_filter, &history_consumer_, history->QueryFilteredURLs(0, time_filter, &history_consumer_,
base::Bind(&SuggestionsSourceTopSites::OnSuggestionsURLsAvailable, base::Bind(&SuggestionsSourceTopSites::OnSuggestionsURLsAvailable,
base::Unretained(this))); base::Unretained(this)));
...@@ -90,3 +94,25 @@ void SuggestionsSourceTopSites::OnSuggestionsURLsAvailable( ...@@ -90,3 +94,25 @@ void SuggestionsSourceTopSites::OnSuggestionsURLsAvailable(
combiner_->OnItemsReady(); combiner_->OnItemsReady();
} }
// static
base::TimeDelta SuggestionsSourceTopSites::GetFilterWidth() {
const CommandLine* cli = CommandLine::ForCurrentProcess();
const std::string filter_width_switch =
cli->GetSwitchValueASCII(switches::kSuggestionNtpFilterWidth);
unsigned int filter_width;
if (base::StringToUint(filter_width_switch, &filter_width))
return base::TimeDelta::FromMinutes(filter_width);
return base::TimeDelta::FromHours(1);
}
// static
history::VisitFilter::SortingOrder
SuggestionsSourceTopSites::GetSortingOrder() {
const CommandLine* cli = CommandLine::ForCurrentProcess();
if (cli->HasSwitch(switches::kSuggestionNtpGaussianFilter))
return history::VisitFilter::ORDER_BY_TIME_GAUSSIAN;
if (cli->HasSwitch(switches::kSuggestionNtpLinearFilter))
return history::VisitFilter::ORDER_BY_TIME_LINEAR;
return history::VisitFilter::ORDER_BY_RECENCY;
}
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "chrome/browser/cancelable_request.h" #include "chrome/browser/cancelable_request.h"
#include "chrome/browser/history/history_types.h" #include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/visit_filter.h"
#include "chrome/browser/ui/webui/ntp/suggestions_source.h" #include "chrome/browser/ui/webui/ntp/suggestions_source.h"
class SuggestionsCombiner; class SuggestionsCombiner;
...@@ -39,6 +40,16 @@ class SuggestionsSourceTopSites : public SuggestionsSource { ...@@ -39,6 +40,16 @@ class SuggestionsSourceTopSites : public SuggestionsSource {
const history::FilteredURLList& data); const history::FilteredURLList& data);
private: private:
// Gets the sorting order from the command-line arguments. Defaults to
// |ORDER_BY_RECENCY| if there are no command-line argument specifying a
// sorting order.
static history::VisitFilter::SortingOrder GetSortingOrder();
// Gets the filter width from the command-line arguments. Defaults to one
// hour if there are no command-line argument setting the filter width.
static base::TimeDelta GetFilterWidth();
// Our combiner. // Our combiner.
SuggestionsCombiner* combiner_; SuggestionsCombiner* combiner_;
......
...@@ -1149,6 +1149,18 @@ const char kSocketReusePolicy[] = "socket-reuse-policy"; ...@@ -1149,6 +1149,18 @@ const char kSocketReusePolicy[] = "socket-reuse-policy";
// Starts the browser maximized, regardless of any previous settings. // Starts the browser maximized, regardless of any previous settings.
const char kStartMaximized[] = "start-maximized"; const char kStartMaximized[] = "start-maximized";
// Controls the width of time-of-day filters on the 'suggested' ntp page, in
// minutes.
const char kSuggestionNtpFilterWidth[] = "suggestion-ntp-filter-width";
// Enables a normal distribution dropoff to the relevancy of visits with respect
// to the time of day.
const char kSuggestionNtpGaussianFilter[] = "suggestion-ntp-gaussian-filter";
// Enables a linear dropoff to the relevancy of visits with respect to the time
// of day.
const char kSuggestionNtpLinearFilter[] = "suggestion-ntp-linear-filter";
// Allows insecure XMPP connections for sync (for testing). // Allows insecure XMPP connections for sync (for testing).
const char kSyncAllowInsecureXmppConnection[] = const char kSyncAllowInsecureXmppConnection[] =
"sync-allow-insecure-xmpp-connection"; "sync-allow-insecure-xmpp-connection";
......
...@@ -309,6 +309,9 @@ extern const char kSilentDumpOnDCHECK[]; ...@@ -309,6 +309,9 @@ extern const char kSilentDumpOnDCHECK[];
extern const char kSimulateUpgrade[]; extern const char kSimulateUpgrade[];
extern const char kSocketReusePolicy[]; extern const char kSocketReusePolicy[];
extern const char kStartMaximized[]; extern const char kStartMaximized[];
extern const char kSuggestionNtpFilterWidth[];
extern const char kSuggestionNtpGaussianFilter[];
extern const char kSuggestionNtpLinearFilter[];
extern const char kSyncAllowInsecureXmppConnection[]; extern const char kSyncAllowInsecureXmppConnection[];
extern const char kSyncInvalidateXmppLogin[]; extern const char kSyncInvalidateXmppLogin[];
extern const char kSyncNotificationMethod[]; extern const char kSyncNotificationMethod[];
......
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