shill: Times-out scan frequency lists.
The frequency/count list that is saved in the default profile used to
save the data for all time. This CL times the data out after 2-3 weeks.
BUG=chromium:236884
TEST=unittest
Change-Id: I8187e2945333ba03eb77c87e087d12f8b4933211
Reviewed-on: https://gerrit.chromium.org/gerrit/51019
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
Commit-Queue: Wade Guthrie <wdg@chromium.org>
diff --git a/mock_time.h b/mock_time.h
index 1d4ef9f..a140f0c 100644
--- a/mock_time.h
+++ b/mock_time.h
@@ -5,11 +5,11 @@
#ifndef SHILL_MOCK_TIME_H_
#define SHILL_MOCK_TIME_H_
+#include "shill/shill_time.h"
+
#include <base/basictypes.h>
#include <gmock/gmock.h>
-#include "shill/shill_time.h"
-
namespace shill {
class MockTime : public Time {
@@ -20,6 +20,7 @@
MOCK_METHOD1(GetTimeMonotonic, int(struct timeval *tv));
MOCK_METHOD2(GetTimeOfDay, int(struct timeval *tv, struct timezone *tz));
MOCK_METHOD0(GetNow, Timestamp());
+ MOCK_CONST_METHOD0(GetSecondsSinceEpoch, time_t());
private:
DISALLOW_COPY_AND_ASSIGN(MockTime);
diff --git a/shill_time.cc b/shill_time.cc
index 7d3519d..45ebf82 100644
--- a/shill_time.cc
+++ b/shill_time.cc
@@ -47,11 +47,11 @@
}
Timestamp Time::GetNow() {
- struct timeval now_monotonic = (const struct timeval){ 0 };
+ struct timeval now_monotonic = (const struct timeval) { 0 };
GetTimeMonotonic(&now_monotonic);
- struct timeval now_wall_clock = (const struct timeval){ 0 };
+ struct timeval now_wall_clock = (const struct timeval) { 0 };
GetTimeOfDay(&now_wall_clock, NULL);
- struct tm local_time = (const struct tm){ 0 };
+ struct tm local_time = (const struct tm) { 0 };
localtime_r(&now_wall_clock.tv_sec, &local_time);
// There're two levels of formatting -- first most of the timestamp is printed
// into |format|, then the resulting string is used to insert the microseconds
@@ -66,4 +66,8 @@
return Timestamp(now_monotonic, wall_clock);
}
+time_t Time::GetSecondsSinceEpoch() const {
+ return time(NULL);
+}
+
} // namespace shill
diff --git a/shill_time.h b/shill_time.h
index c6b4a38..d145dff 100644
--- a/shill_time.h
+++ b/shill_time.h
@@ -6,6 +6,7 @@
#define SHILL_TIME_H_
#include <sys/time.h>
+#include <time.h>
#include <string>
@@ -18,7 +19,7 @@
// be used for presenting the time in human-readable format. Note that the
// monotonic clock does not necessarily advance during suspend.
struct Timestamp {
- Timestamp() : monotonic((const struct timeval){ 0 }) {}
+ Timestamp() : monotonic((const struct timeval) { 0 }) {}
Timestamp(const struct timeval &in_monotonic,
const std::string &in_wall_clock)
: monotonic(in_monotonic),
@@ -44,6 +45,8 @@
// Returns a snapshot of the current time.
virtual Timestamp GetNow();
+ virtual time_t GetSecondsSinceEpoch() const;
+
protected:
Time();
diff --git a/wifi_provider.cc b/wifi_provider.cc
index 79a6c6d..7004a7e 100644
--- a/wifi_provider.cc
+++ b/wifi_provider.cc
@@ -12,7 +12,10 @@
#include <vector>
#include <base/bind.h>
+#include <base/format_macros.h>
#include <base/string_number_conversions.h>
+#include <base/string_split.h>
+#include <base/string_util.h>
#include "shill/error.h"
#include "shill/event_dispatcher.h"
@@ -22,12 +25,14 @@
#include "shill/manager.h"
#include "shill/metrics.h"
#include "shill/profile.h"
+#include "shill/shill_time.h"
#include "shill/store_interface.h"
#include "shill/technology.h"
#include "shill/wifi_endpoint.h"
#include "shill/wifi_service.h"
using base::Bind;
+using base::SplitString;
using std::set;
using std::string;
using std::vector;
@@ -45,9 +50,15 @@
"security mode is unsupported";
const char WiFiProvider::kManagerErrorUnsupportedServiceMode[] =
"service mode is unsupported";
-const char WiFiProvider::kFrequencyDelimiter[] = ":";
+const char WiFiProvider::kFrequencyDelimiter = ':';
+const char WiFiProvider::kStartWeekHeader[] = "@";
+const time_t WiFiProvider::kIllegalStartWeek =
+ std::numeric_limits<time_t>::max();
const char WiFiProvider::kStorageId[] = "provider_of_wifi";
const char WiFiProvider::kStorageFrequencies[] = "Frequencies";
+const int WiFiProvider::kMaxStorageFrequencies = 20;
+const time_t WiFiProvider::kWeeksToKeepFrequencyCounts = 3;
+const time_t WiFiProvider::kSecondsPerWeek = 60 * 60 * 24 * 7;
WiFiProvider::WiFiProvider(ControlInterface *control_interface,
EventDispatcher *dispatcher,
@@ -58,7 +69,8 @@
metrics_(metrics),
manager_(manager),
running_(false),
- total_frequency_connections_(-1L) {}
+ total_frequency_connections_(-1L),
+ time_(Time::GetInstance()) {}
WiFiProvider::~WiFiProvider() {}
@@ -327,26 +339,61 @@
// default profile except for autotests where a test_profile is pushed. This
// may need to be modified for that case.
if (is_default_profile) {
- vector<string> frequencies;
- if (storage->GetStringList(kStorageId,
- kStorageFrequencies,
- &frequencies)) {
- StringListToFrequencyMap(frequencies, &connect_count_by_frequency_);
- total_frequency_connections_ = 0L;
- WiFiProvider::ConnectFrequencyMap::const_iterator i;
- for (i = connect_count_by_frequency_.begin();
- i != connect_count_by_frequency_.end();
- ++i) {
- total_frequency_connections_ += i->second;
+ COMPILE_ASSERT(kMaxStorageFrequencies > kWeeksToKeepFrequencyCounts,
+ persistently_storing_more_frequencies_than_we_can_hold);
+ total_frequency_connections_ = 0L;
+ connect_count_by_frequency_.clear();
+ time_t this_week = time_->GetSecondsSinceEpoch() / kSecondsPerWeek;
+ for (int freq = 0; freq < kMaxStorageFrequencies; ++freq) {
+ ConnectFrequencyMap connect_count_by_frequency;
+ string freq_string = StringPrintf("%s%d", kStorageFrequencies, freq);
+ vector<string> frequencies;
+ if (!storage->GetStringList(kStorageId, freq_string, &frequencies)) {
+ SLOG(WiFi, 7) << "Frequency list " << freq_string << " not found";
+ break;
+ }
+ time_t start_week = StringListToFrequencyMap(frequencies,
+ &connect_count_by_frequency);
+ if (start_week == kIllegalStartWeek) {
+ continue; // |StringListToFrequencyMap| will have output an error msg.
+ }
+
+ if (start_week > this_week) {
+ LOG(WARNING) << "Discarding frequency count info from the future";
+ continue;
+ }
+ connect_count_by_frequency_dated_[start_week] =
+ connect_count_by_frequency;
+
+ for (const auto &freq_count :
+ connect_count_by_frequency_dated_[start_week]) {
+ connect_count_by_frequency_[freq_count.first] += freq_count.second;
+ total_frequency_connections_ += freq_count.second;
}
}
+ SLOG(WiFi, 7) << __func__ << " - total count="
+ << total_frequency_connections_;
}
}
bool WiFiProvider::Save(StoreInterface *storage) const {
- vector<string> frequencies;
- FrequencyMapToStringList(connect_count_by_frequency_, &frequencies);
- storage->SetStringList(kStorageId, kStorageFrequencies, frequencies);
+ int freq = 0;
+ // Iterating backwards since I want to make sure that I get the newest data.
+ ConnectFrequencyMapDated::const_reverse_iterator freq_count;
+ for (freq_count = connect_count_by_frequency_dated_.crbegin();
+ freq_count != connect_count_by_frequency_dated_.crend();
+ ++freq_count) {
+ vector<string> frequencies;
+ FrequencyMapToStringList(freq_count->first, freq_count->second,
+ &frequencies);
+ string freq_string = StringPrintf("%s%d", kStorageFrequencies, freq);
+ storage->SetStringList(kStorageId, freq_string, frequencies);
+ if (++freq >= kMaxStorageFrequencies) {
+ LOG(WARNING) << "Internal frequency count list has more entries than the "
+ << "string list we had allocated for it.";
+ break;
+ }
+ }
return true;
}
@@ -464,68 +511,106 @@
}
// static
-void WiFiProvider::StringListToFrequencyMap(const vector<string> &strings,
+time_t WiFiProvider::StringListToFrequencyMap(const vector<string> &strings,
ConnectFrequencyMap *numbers) {
if (!numbers) {
LOG(ERROR) << "Null |numbers| parameter";
- return;
+ return kIllegalStartWeek;
}
- vector<string>::const_iterator i;
- for (i = strings.begin(); i != strings.end(); ++i) {
- size_t delimiter = i->find(kFrequencyDelimiter);
- if (delimiter == i->npos) {
- LOG(WARNING) << "Found no '" << kFrequencyDelimiter << "' in '"
- << *i << "'";
- continue;
- }
- uint16 freq = atoi(i->c_str());
- uint64 connections = atoll(i->c_str() + delimiter + 1);
- (*numbers)[freq] = connections;
+ // Extract the start week from the first string.
+ vector<string>::const_iterator strings_it = strings.begin();
+ if (strings_it == strings.end()) {
+ SLOG(WiFi, 7) << "Empty |strings|.";
+ return kIllegalStartWeek;
}
+ time_t start_week = GetStringListStartWeek(*strings_it);
+ if (start_week == kIllegalStartWeek) {
+ return kIllegalStartWeek;
+ }
+
+ // Extract the frequency:count values from the remaining strings.
+ for (++strings_it; strings_it != strings.end(); ++strings_it) {
+ ParseStringListFreqCount(*strings_it, numbers);
+ }
+ return start_week;
}
// static
-void WiFiProvider::FrequencyMapToStringList(const ConnectFrequencyMap &numbers,
+time_t WiFiProvider::GetStringListStartWeek(const string &week_string) {
+ if (!StartsWithASCII(week_string, kStartWeekHeader, false)) {
+ LOG(ERROR) << "Found no leading '" << kStartWeekHeader << "' in '"
+ << week_string << "'";
+ return kIllegalStartWeek;
+ }
+ return atoll(week_string.c_str() + 1);
+}
+
+// static
+void WiFiProvider::ParseStringListFreqCount(const string &freq_count_string,
+ ConnectFrequencyMap *numbers) {
+ vector<string> freq_count;
+ SplitString(freq_count_string, kFrequencyDelimiter, &freq_count);
+ if (freq_count.size() != 2) {
+ LOG(WARNING) << "Found " << freq_count.size() - 1 << " '"
+ << kFrequencyDelimiter << "' in '" << freq_count_string
+ << "'. Expected 1.";
+ return;
+ }
+ uint16 freq = atoi(freq_count[0].c_str());
+ uint64 connections = atoll(freq_count[1].c_str());
+ (*numbers)[freq] = connections;
+}
+
+// static
+void WiFiProvider::FrequencyMapToStringList(time_t start_week,
+ const ConnectFrequencyMap &numbers,
vector<string> *strings) {
if (!strings) {
LOG(ERROR) << "Null |strings| parameter";
return;
}
- ConnectFrequencyMap::const_iterator i;
- for (i = numbers.begin(); i != numbers.end(); ++i) {
+ strings->push_back(StringPrintf("%s%" PRIu64, kStartWeekHeader,
+ static_cast<uint64_t>(start_week)));
+
+ for (const auto &freq_conn : numbers) {
// Use base::Int64ToString() instead of using something like "%llu"
// (not correct for native 64 bit architectures) or PRId64 (does not
// work correctly using cros_workon_make due to include intricacies).
- string result = StringPrintf("%u%s%s", i->first, kFrequencyDelimiter,
- base::Int64ToString(i->second).c_str());
- strings->push_back(result);
+ strings->push_back(StringPrintf("%u%c%s",
+ freq_conn.first, kFrequencyDelimiter,
+ base::Int64ToString(freq_conn.second).c_str()));
}
}
void WiFiProvider::IncrementConnectCount(uint16 frequency_mhz) {
- // Freeze the accumulation of frequency counts when the total maxes-out.
- // This ensures that no count wraps and that the relative values are
- // consistent.
- // TODO(wdg): Replace this, simple, 'forever' collection of connection
- // statistics with a more clever 'aging' algorithm. crbug.com/227233
- if (total_frequency_connections_ + 1 == std::numeric_limits<int64_t>::max()) {
- LOG(ERROR) << "Shill has logged " << total_frequency_connections_
- << " connections -- must be an error. Resetting connection "
- << "count.";
- connect_count_by_frequency_.clear();
- }
+ CHECK(total_frequency_connections_ < std::numeric_limits<int64_t>::max());
- int64 previous_value = 0;
- if (ContainsKey(connect_count_by_frequency_, frequency_mhz)) {
- previous_value = connect_count_by_frequency_[frequency_mhz];
- }
-
- connect_count_by_frequency_[frequency_mhz] = ++previous_value;
+ ++connect_count_by_frequency_[frequency_mhz];
++total_frequency_connections_;
- manager_->UpdateWiFiProvider();
+ time_t this_week = time_->GetSecondsSinceEpoch() / kSecondsPerWeek;
+ ++connect_count_by_frequency_dated_[this_week][frequency_mhz];
+
+ ConnectFrequencyMapDated::iterator oldest =
+ connect_count_by_frequency_dated_.begin();
+ time_t oldest_legal_week = this_week - kWeeksToKeepFrequencyCounts;
+ while (oldest->first < oldest_legal_week) {
+ SLOG(WiFi, 7) << "Discarding frequency count info that's "
+ << this_week - oldest->first << " weeks old";
+ for (const auto &freq_count : oldest->second) {
+ connect_count_by_frequency_[freq_count.first] -= freq_count.second;
+ if (connect_count_by_frequency_[freq_count.first] <= 0) {
+ connect_count_by_frequency_.erase(freq_count.first);
+ }
+ total_frequency_connections_ -= freq_count.second;
+ }
+ connect_count_by_frequency_dated_.erase(oldest);
+ oldest = connect_count_by_frequency_dated_.begin();
+ }
+
+ manager_->UpdateWiFiProvider();
metrics_->SendToUMA(
Metrics::kMetricFrequenciesConnectedEver,
connect_count_by_frequency_.size(),
diff --git a/wifi_provider.h b/wifi_provider.h
index 79e375c..b77ed96 100644
--- a/wifi_provider.h
+++ b/wifi_provider.h
@@ -5,6 +5,8 @@
#ifndef SHILL_WIFI_PROVIDER_H_
#define SHILL_WIFI_PROVIDER_H_
+#include <time.h>
+
#include <deque>
#include <map>
@@ -22,6 +24,7 @@
class Manager;
class Metrics;
class StoreInterface;
+class Time;
class WiFiEndpoint;
class WiFiService;
@@ -30,7 +33,12 @@
// (created due to user or storage configuration) Services.
class WiFiProvider {
public:
+ static const char kStorageFrequencies[];
+ static const int kMaxStorageFrequencies;
typedef std::map<uint16, int64> ConnectFrequencyMap;
+ // The key to |ConnectFrequencyMapDated| is the number of days since the
+ // Epoch.
+ typedef std::map<time_t, ConnectFrequencyMap> ConnectFrequencyMapDated;
struct FrequencyCount {
FrequencyCount() : frequency(0), connection_count(0) {}
FrequencyCount(uint16 freq, size_t conn)
@@ -111,10 +119,16 @@
private:
friend class WiFiProviderTest;
+ FRIEND_TEST(WiFiProviderTest, FrequencyMapAgingIllegalDay);
+ FRIEND_TEST(WiFiProviderTest, FrequencyMapBasicAging);
FRIEND_TEST(WiFiProviderTest, FrequencyMapToStringList);
+ FRIEND_TEST(WiFiProviderTest, FrequencyMapToStringListEmpty);
+ FRIEND_TEST(WiFiProviderTest, IncrementConnectCount);
+ FRIEND_TEST(WiFiProviderTest, IncrementConnectCountCreateNew);
FRIEND_TEST(WiFiProviderTest, LoadAndFixupServiceEntries);
FRIEND_TEST(WiFiProviderTest, LoadAndFixupServiceEntriesNothingToDo);
FRIEND_TEST(WiFiProviderTest, StringListToFrequencyMap);
+ FRIEND_TEST(WiFiProviderTest, StringListToFrequencyMapEmpty);
typedef std::map<const WiFiEndpoint *, WiFiServiceRefPtr> EndpointServiceMap;
@@ -123,9 +137,12 @@
static const char kManagerErrorSSIDRequired[];
static const char kManagerErrorUnsupportedSecurityMode[];
static const char kManagerErrorUnsupportedServiceMode[];
- static const char kFrequencyDelimiter[];
+ static const char kFrequencyDelimiter;
+ static const char kStartWeekHeader[];
+ static const time_t kIllegalStartWeek;
static const char kStorageId[];
- static const char kStorageFrequencies[];
+ static const time_t kWeeksToKeepFrequencyCounts;
+ static const time_t kSecondsPerWeek;
// Add a service to the service_ vector and register it with the Manager.
WiFiServiceRefPtr AddService(const std::vector<uint8_t> &ssid,
@@ -157,14 +174,29 @@
// Converts frequency profile information from a list of strings of the form
// "frequency:connection_count" to a form consistent with
- // |connect_count_by_frequency_|
- static void StringListToFrequencyMap(const std::vector<std::string> &strings,
+ // |connect_count_by_frequency_|. The first string must be of the form
+ // [nnn] where |nnn| is a positive integer that represents the creation time
+ // (number of days since the Epoch) of the data.
+ static time_t StringListToFrequencyMap(
+ const std::vector<std::string> &strings,
+ ConnectFrequencyMap *numbers);
+
+ // Extracts the start week from the first string in the StringList for
+ // |StringListToFrequencyMap|.
+ static time_t GetStringListStartWeek(const std::string &week_string);
+
+ // Extracts frequency and connection count from a string from the StringList
+ // for |StringListToFrequencyMap|. Places those values in |numbers|.
+ static void ParseStringListFreqCount(const std::string &freq_count_string,
ConnectFrequencyMap *numbers);
// Converts frequency profile information from a form consistent with
// |connect_count_by_frequency_| to a list of strings of the form
- // "frequency:connection_count"
- static void FrequencyMapToStringList(const ConnectFrequencyMap &numbers,
+ // "frequency:connection_count". The |creation_day| is the day that the
+ // data was first createed (represented as the number of days since the
+ // Epoch).
+ static void FrequencyMapToStringList(time_t creation_day,
+ const ConnectFrequencyMap &numbers,
std::vector<std::string> *strings);
ControlInterface *control_interface_;
@@ -181,10 +213,14 @@
// successful connection has been made at that frequency. Absent frequencies
// have not had a successful connection.
ConnectFrequencyMap connect_count_by_frequency_;
+ // A number of entries of |ConnectFrequencyMap| stored by date of creation.
+ ConnectFrequencyMapDated connect_count_by_frequency_dated_;
// Count of successful wifi connections we've made.
int64_t total_frequency_connections_;
+ Time *time_;
+
DISALLOW_COPY_AND_ASSIGN(WiFiProvider);
};
diff --git a/wifi_provider_unittest.cc b/wifi_provider_unittest.cc
index 8ff8f26..64aaf13 100644
--- a/wifi_provider_unittest.cc
+++ b/wifi_provider_unittest.cc
@@ -4,33 +4,39 @@
#include "shill/wifi_provider.h"
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
#include <base/string_number_conversions.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
-#include <set>
-#include <string>
-#include <vector>
-
#include "shill/ieee80211.h"
#include "shill/mock_event_dispatcher.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_profile.h"
#include "shill/mock_store.h"
+#include "shill/mock_time.h"
#include "shill/mock_wifi_service.h"
#include "shill/nice_mock_control.h"
#include "shill/technology.h"
#include "shill/wifi_endpoint.h"
#include "shill/wpa_supplicant.h"
+using std::map;
using std::set;
using std::string;
using std::vector;
using ::testing::_;
using ::testing::ContainerEq;
+using ::testing::Eq;
+using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
@@ -39,6 +45,15 @@
namespace shill {
+namespace {
+
+const time_t kFirstWeek = 50;
+const char kIllegalDayProfile[] = "IllegalDay";
+const time_t kSecondsPerWeek = 60 * 60 * 24 * 7;
+const time_t kTestDays = 20;
+
+} // namespace
+
class WiFiProviderTest : public testing::Test {
public:
WiFiProviderTest()
@@ -48,7 +63,33 @@
provider_(&control_, &dispatcher_, &metrics_, &manager_),
profile_(
new NiceMock<MockProfile>(&control_, &metrics_, &manager_, "")),
- storage_entry_index_(0) {}
+ storage_entry_index_(0) {
+ provider_.time_ = &time_;
+
+ string freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies,
+ 0);
+ profile_frequency_data_[freq_string].push_back(
+ base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek)));
+ profile_frequency_data_[freq_string].push_back("5001:1");
+ profile_frequency_data_[freq_string].push_back("5002:2");
+
+ freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1);
+ profile_frequency_data_[freq_string].push_back(
+ base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek) + 1));
+ // Overlap one entry with previous block.
+ profile_frequency_data_[freq_string].push_back("5001:1");
+ profile_frequency_data_[freq_string].push_back("6001:1");
+ profile_frequency_data_[freq_string].push_back("6002:2");
+
+ freq_string = StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2);
+ profile_frequency_data_[freq_string].push_back(
+ base::StringPrintf("@%" PRIu64, static_cast<uint64_t>(kFirstWeek) + 2));
+ profile_frequency_data_[freq_string].push_back("7001:1");
+ profile_frequency_data_[freq_string].push_back("7002:2");
+
+ profile_frequency_data_[kIllegalDayProfile].push_back("7001:1");
+ profile_frequency_data_[kIllegalDayProfile].push_back("7002:2");
+ }
virtual ~WiFiProviderTest() {}
@@ -56,6 +97,32 @@
EXPECT_CALL(*profile_, GetConstStorage()).WillRepeatedly(Return(&storage_));
}
+ bool GetStringList(const std::string &/*group*/,
+ const std::string &key,
+ std::vector<std::string> *value) {
+ if (!value) {
+ return false;
+ }
+ if (ContainsKey(profile_frequency_data_, key)) {
+ *value = profile_frequency_data_[key];
+ return true;
+ }
+ return false;
+ }
+
+ bool GetIllegalDayStringList(const std::string &/*group*/,
+ const std::string &key,
+ std::vector<std::string> *value) {
+ if (!value) {
+ return false;
+ }
+ if (ContainsKey(profile_frequency_data_, key)) {
+ *value = profile_frequency_data_[kIllegalDayProfile];
+ return true;
+ }
+ return false;
+ }
+
protected:
typedef scoped_refptr<MockWiFiService> MockWiFiServiceRefPtr;
@@ -67,6 +134,10 @@
provider_.LoadAndFixupServiceEntries(&storage_, is_default_profile);
}
+ void Save() {
+ provider_.Save(&storage_);
+ }
+
const vector<WiFiServiceRefPtr> GetServices() {
return provider_.services_;
}
@@ -202,7 +273,7 @@
// are also provided, here, in sorted order to match the frequency map
// (iterators for which will provide them in frequency-sorted order).
static const char *kStrings[] = {
- "5180:14", "5240:16", "5745:7", "5765:4", "5785:14", "5805:5"
+ "@20", "5180:14", "5240:16", "5745:7", "5765:4", "5785:14", "5805:5"
};
if (!strings) {
LOG(ERROR) << "NULL |strings|.";
@@ -235,9 +306,41 @@
}
}
+ void LoadConnectCountByFrequency(time_t today_seconds) {
+ provider_.time_ = &time_;
+ EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(today_seconds));
+ const string kGroupId =
+ StringPrintf("%s_0_0_%s_%s",
+ flimflam::kTypeWifi,
+ flimflam::kModeManaged,
+ flimflam::kSecurityNone);
+ EXPECT_CALL(storage_,
+ GetString(kGroupId, _, _)).WillRepeatedly(Return(true));
+ set<string> groups;
+ groups.insert(kGroupId);
+ EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
+ // Provide data for block[0] through block[2] (block[3] is empty).
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+
+ LoadAndFixupServiceEntries(true);
+ }
+
+ map<string, vector<string>> profile_frequency_data_;
NiceMockControl control_;
MockEventDispatcher dispatcher_;
MockMetrics metrics_;
+ MockTime time_;
StrictMock<MockManager> manager_;
WiFiProvider provider_;
scoped_refptr<MockProfile> profile_;
@@ -1018,8 +1121,17 @@
groups.insert(kGroupId);
EXPECT_CALL(storage_, GetGroups()).WillRepeatedly(Return(groups));
EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
- WiFiProvider::kStorageFrequencies, _))
- .WillOnce(Return(true));
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+ WillOnce(Return(false));
LoadAndFixupServiceEntries(true);
Mock::VerifyAndClearExpectations(&metrics_);
@@ -1046,8 +1158,17 @@
groups.insert(kGroupId);
EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
- WiFiProvider::kStorageFrequencies, _))
- .WillOnce(Return(true));
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+ WillOnce(Return(false));
LoadAndFixupServiceEntries(true);
}
@@ -1105,22 +1226,199 @@
vector<string> strings;
BuildFreqCountStrings(&strings);
WiFiProvider::ConnectFrequencyMap frequencies_result;
- WiFiProvider::StringListToFrequencyMap(strings, &frequencies_result);
+ time_t days = WiFiProvider::StringListToFrequencyMap(strings,
+ &frequencies_result);
WiFiProvider::ConnectFrequencyMap frequencies_expect;
BuildFreqCountMap(&frequencies_expect);
EXPECT_THAT(frequencies_result, ContainerEq(frequencies_expect));
+ EXPECT_EQ(days, kTestDays);
+}
+
+TEST_F(WiFiProviderTest, StringListToFrequencyMapEmpty) {
+ vector<string> strings;
+ strings.push_back("@50");
+ WiFiProvider::ConnectFrequencyMap frequencies_result;
+ time_t days = WiFiProvider::StringListToFrequencyMap(strings,
+ &frequencies_result);
+ EXPECT_TRUE(frequencies_result.empty());
+ EXPECT_EQ(days, 50);
}
TEST_F(WiFiProviderTest, FrequencyMapToStringList) {
WiFiProvider::ConnectFrequencyMap frequencies;
BuildFreqCountMap(&frequencies);
vector<string> strings_result;
- WiFiProvider::FrequencyMapToStringList(frequencies, &strings_result);
+ WiFiProvider::FrequencyMapToStringList(kTestDays, frequencies,
+ &strings_result);
vector<string> strings_expect;
BuildFreqCountStrings(&strings_expect);
EXPECT_THAT(strings_result, ContainerEq(strings_expect));
}
+TEST_F(WiFiProviderTest, FrequencyMapToStringListEmpty) {
+ WiFiProvider::ConnectFrequencyMap frequencies;
+ vector<string> strings_result;
+ WiFiProvider::FrequencyMapToStringList(kTestDays, frequencies,
+ &strings_result);
+ EXPECT_EQ(1, strings_result.size());
+ EXPECT_EQ(*strings_result.begin(), "@20");
+}
+
+TEST_F(WiFiProviderTest, FrequencyMapBasicAging) {
+ const time_t kThisWeek = kFirstWeek +
+ WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+ LoadConnectCountByFrequency(kThisWeek * kSecondsPerWeek);
+
+ // Make sure we have data for all 3 blocks.
+ WiFiProvider::ConnectFrequencyMap expected;
+ expected[5001] = 2;
+ expected[5002] = 2;
+ expected[6001] = 1;
+ expected[6002] = 2;
+ expected[7001] = 1;
+ expected[7002] = 2;
+ EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+
+ // And, then, make sure we output the expected blocks of data.
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0)])));
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1)])));
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2)])));
+ vector<string> frequencies;
+ frequencies.push_back(base::StringPrintf("@%" PRIu64,
+ static_cast<uint64_t>(kThisWeek)));
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(frequencies))).Times(0);
+ Save();
+}
+
+TEST_F(WiFiProviderTest, FrequencyMapAgingIllegalDay) {
+ provider_.time_ = &time_;
+ const time_t kThisWeek = kFirstWeek +
+ WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+ const time_t kThisWeekSeconds = kThisWeek * kSecondsPerWeek;
+ EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(kThisWeekSeconds));
+ const string kGroupId =
+ StringPrintf("%s_0_0_%s_%s",
+ flimflam::kTypeWifi,
+ flimflam::kModeManaged,
+ flimflam::kSecurityNone);
+ EXPECT_CALL(storage_,
+ GetString(kGroupId, _, _)).WillRepeatedly(Return(true));
+ set<string> groups;
+ groups.insert(kGroupId);
+ // Instead of block[1], return a block without the date.
+ EXPECT_CALL(storage_, GetGroups()).WillOnce(Return(groups));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetIllegalDayStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+ EXPECT_CALL(storage_, GetStringList(WiFiProvider::kStorageId,
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 3), _)).
+ WillOnce(Invoke(this, &WiFiProviderTest::GetStringList));
+
+ LoadAndFixupServiceEntries(true);
+
+ // Verify that the received information only includes block[0] and block[2].
+ WiFiProvider::ConnectFrequencyMap expected;
+ expected[5001] = 1;
+ expected[5002] = 2;
+ expected[7001] = 1;
+ expected[7002] = 2;
+ EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+
+ // And, then, make sure we output the expected blocks of data.
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 0)])));
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 1)]))).
+ Times(0);
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(profile_frequency_data_[
+ StringPrintf("%s%d", WiFiProvider::kStorageFrequencies, 2)])));
+ vector<string> frequencies;
+ frequencies.push_back(base::StringPrintf("@%" PRIu64,
+ static_cast<uint64_t>(kThisWeek)));
+ EXPECT_CALL(storage_, SetStringList(WiFiProvider::kStorageId, _,
+ Eq(frequencies))).Times(0);
+ Save();
+}
+
+TEST_F(WiFiProviderTest, IncrementConnectCount) {
+ const time_t kThisWeek = kFirstWeek +
+ WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+ const time_t kThisWeekSeconds = kThisWeek * kSecondsPerWeek;
+ LoadConnectCountByFrequency(kThisWeekSeconds);
+
+ EXPECT_CALL(time_, GetSecondsSinceEpoch()).WillOnce(Return(kThisWeekSeconds));
+ EXPECT_CALL(manager_, UpdateWiFiProvider());
+ EXPECT_CALL(metrics_, SendToUMA(_, _, _, _, _));
+ time_t newest_week_at_start =
+ provider_.connect_count_by_frequency_dated_.crbegin()->first;
+ provider_.IncrementConnectCount(6002);
+
+ // Make sure we have data for all 3 blocks.
+ WiFiProvider::ConnectFrequencyMap expected;
+ expected[5001] = 2;
+ expected[5002] = 2;
+ expected[6001] = 1;
+ expected[6002] = 3;
+ expected[7001] = 1;
+ expected[7002] = 2;
+ EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+ // Make sure we didn't delete the oldest block.
+ EXPECT_TRUE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+ kFirstWeek));
+ // Make sure we didn't create a new block.
+ time_t newest_week_at_end =
+ provider_.connect_count_by_frequency_dated_.crbegin()->first;
+ EXPECT_EQ(newest_week_at_start, newest_week_at_end);
+}
+
+TEST_F(WiFiProviderTest, IncrementConnectCountCreateNew) {
+ time_t this_week = kFirstWeek + WiFiProvider::kWeeksToKeepFrequencyCounts - 1;
+ LoadConnectCountByFrequency(this_week * kSecondsPerWeek);
+
+ this_week += 2;
+ EXPECT_CALL(time_, GetSecondsSinceEpoch()).
+ WillOnce(Return(this_week * kSecondsPerWeek));
+ EXPECT_CALL(manager_, UpdateWiFiProvider());
+ EXPECT_CALL(metrics_, SendToUMA(_, _, _, _, _));
+ time_t newest_week_at_start =
+ provider_.connect_count_by_frequency_dated_.crbegin()->first;
+ provider_.IncrementConnectCount(6001);
+
+ // Make sure we have data for newest 2 blocks (only).
+ WiFiProvider::ConnectFrequencyMap expected;
+ expected[5001] = 1;
+ expected[6001] = 2;
+ expected[6002] = 2;
+ expected[7001] = 1;
+ expected[7002] = 2;
+ EXPECT_THAT(provider_.connect_count_by_frequency_, ContainerEq(expected));
+ // Verify that the oldest block is gone.
+ EXPECT_FALSE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+ kFirstWeek));
+ // Make sure we created a new block and that it is for the current week.
+ time_t newest_week_at_end =
+ provider_.connect_count_by_frequency_dated_.crbegin()->first;
+ EXPECT_NE(newest_week_at_start, newest_week_at_end);
+ EXPECT_TRUE(ContainsKey(provider_.connect_count_by_frequency_dated_,
+ this_week));
+}
+
} // namespace shill