// Copyright 2010-2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "usage_stats/usage_stats_uploader.h"
#include <algorithm>
#include "base/encryptor.h"
#include "base/mac_util.h"
#include "base/mutex.h"
#include "base/singleton.h"
#include "base/util.h"
#include "base/version.h"
#include "base/win_util.h"
#include "config/stats_config_util.h"
#include "storage/registry.h"
#include "usage_stats/usage_stats.h"
#include "usage_stats/usage_stats.pb.h"
#include "usage_stats/upload_util.h"
namespace mozc {
namespace usage_stats {
namespace {
const char kRegistryPrefix[] = "usage_stats.";
const char kLastUploadKey[] = "last_upload";
const char kClientIdKey[] = "client_id";
const uint32 kSendInterval = 23 * 60 * 60; // 23 hours
#include "usage_stats/usage_stats_list.h"
// creates randomly generated new client id and insert it to registry
void CreateAndInsertClientId(string *output) {
DCHECK(output);
const size_t kClientIdSize = 16;
char rand_str[kClientIdSize + 1];
Util::GetSecureRandomAsciiSequence(rand_str, sizeof(rand_str));
rand_str[kClientIdSize] = '\0';
*output = rand_str;
string encrypted;
if (!Encryptor::ProtectData(*output, &encrypted)) {
LOG(ERROR) << "cannot encrypt client_id";
return;
}
const string key = string(kRegistryPrefix) + string(kClientIdKey);
if (!storage::Registry::Insert(key, encrypted)) {
LOG(ERROR) << "cannot insert client_id to registry";
return;
}
return;
}
// default implementation for client id
// this refers the registry and returns stored id if the id is found.
class ClientIdImpl : public ClientIdInterface {
public:
ClientIdImpl();
virtual ~ClientIdImpl();
void GetClientId(string *output);
};
ClientIdImpl::ClientIdImpl() {}
ClientIdImpl::~ClientIdImpl() {}
void ClientIdImpl::GetClientId(string *output) {
DCHECK(output);
string encrypted;
const string key = string(kRegistryPrefix) + string(kClientIdKey);
if (!storage::Registry::Lookup(key, &encrypted)) {
LOG(ERROR) << "clientid lookup failed";
CreateAndInsertClientId(output);
return;
}
if (!Encryptor::UnprotectData(encrypted, output)) {
LOG(ERROR) << "unprotect clientid failed";
CreateAndInsertClientId(output);
return;
}
// lookup and unprotect succeeded
return;
}
ClientIdInterface *g_client_id_handler = NULL;
Mutex g_mutex;
ClientIdInterface &GetClientIdHandler() {
scoped_lock l(&g_mutex);
if (g_client_id_handler == NULL) {
return *(Singleton<ClientIdImpl>::get());
} else {
return *g_client_id_handler;
}
}
} // namespace
// 1 min
const uint32 UsageStatsUploader::kDefaultSchedulerDelay = 60*1000;
// 5 min
const uint32 UsageStatsUploader::kDefaultSchedulerRandomDelay = 5*60*1000;
// 5 min
const uint32 UsageStatsUploader::kDefaultScheduleInterval = 5*60*1000;
// 2 hours
const uint32 UsageStatsUploader::kDefaultScheduleMaxInterval = 2*60*60*1000;
void UsageStatsUploader::SetClientIdHandler(
ClientIdInterface *client_id_handler) {
scoped_lock l(&g_mutex);
g_client_id_handler = client_id_handler;
}
void UsageStatsUploader::LoadStats(UploadUtil *uploader) {
DCHECK(uploader);
string stats_str;
Stats stats;
for (size_t i = 0; i < arraysize(kStatsList); ++i) {
const string key = string(kRegistryPrefix) + string(kStatsList[i]);
if (!storage::Registry::Lookup(key, &stats_str)) {
VLOG(4) << "stats not found: " << kStatsList[i];
continue;
}
if (!stats.ParseFromString(stats_str)) {
LOG(ERROR) << "ParseFromString failed.";
continue;
}
const string &name = stats.name();
switch (stats.type()) {
case Stats::COUNT:
DCHECK(stats.has_count()) << name;
uploader->AddCountValue(name, stats.count());
break;
case Stats::TIMING:
DCHECK(stats.has_num_timings()) << name;
DCHECK(stats.has_avg_time()) << name;
DCHECK(stats.has_min_time()) << name;
DCHECK(stats.has_max_time()) << name;
uploader->AddTimingValue(name, stats.num_timings(), stats.avg_time(),
stats.min_time(), stats.max_time());
break;
case Stats::INTEGER:
DCHECK(stats.has_int_value()) << name;
uploader->AddIntegerValue(name, stats.int_value());
break;
case Stats::BOOLEAN:
DCHECK(stats.has_boolean_value()) << name;
uploader->AddBooleanValue(name, stats.boolean_value());
break;
default:
VLOG(3) << "stats " << name << " has no type";
break;
}
}
}
void UsageStatsUploader::GetClientId(string *output) {
GetClientIdHandler().GetClientId(output);
}
bool UsageStatsUploader::Send(void *data) {
UsageStats::Sync();
const uint32 current_sec = static_cast<uint32>(Util::GetTime());
uint32 last_upload_sec;
const string upload_key = string(kRegistryPrefix) + string(kLastUploadKey);
if (!storage::Registry::Lookup(upload_key, &last_upload_sec) ||
last_upload_sec > current_sec) {
// invalid value: time zone changed etc.
// quit here just saving current time and clear stats
UsageStats::ClearStats();
if (!storage::Registry::Insert(upload_key, current_sec)) {
LOG(ERROR) << "cannot save current_time to registry";
return false;
} else {
VLOG(2) << "saved current_time to registry";
return true;
}
}
// if no upload_usage_stats, we simply clear stats here.
if (!StatsConfigUtil::IsEnabled()) {
UsageStats::ClearStats();
LOG(WARNING) << "no send_stats";
return false;
}
const uint32 elapsed_sec = current_sec - last_upload_sec;
DCHECK_GE(elapsed_sec, 0);
if (elapsed_sec < kSendInterval) {
LOG(WARNING) << "not enough time has passed yet";
return false;
}
vector<pair<string, string> > params;
params.push_back(make_pair("hl", "ja"));
params.push_back(make_pair("v", Version::GetMozcVersion()));
string client_id;
GetClientId(&client_id);
DCHECK(!client_id.empty());
params.push_back(make_pair("client_id", client_id));
params.push_back(make_pair("os_ver", Util::GetOSVersionString()));
// Get total memory in MB.
const uint32 memory_in_mb = Util::GetTotalPhysicalMemory() / (1024 * 1024);
UsageStats::SetInteger("TotalPhysicalMemory", memory_in_mb);
UploadUtil uploader;
uploader.SetHeader("Daily", elapsed_sec, params);
#ifdef OS_WINDOWS
UsageStats::SetBoolean("WindowsX64", Util::IsWindowsX64());
{
// get msctf version
int major, minor, build, revision;
const wchar_t kDllName[] = L"msctf.dll";
wstring path = Util::GetSystemDir();
path += L"\\";
path += kDllName;
if (Util::GetFileVersion(path, &major, &minor, &build, &revision)) {
UsageStats::SetInteger("MsctfVerMajor", major);
UsageStats::SetInteger("MsctfVerMinor", minor);
UsageStats::SetInteger("MsctfVerBuild", build);
UsageStats::SetInteger("MsctfVerRevision", revision);
} else {
LOG(ERROR) << "get file version for msctf.dll failed";
}
}
UsageStats::SetBoolean("CuasEnabled", WinUtil::IsCuasEnabled());
#endif // OS_WINDOWS
#ifdef OS_MACOSX
UsageStats::SetBoolean("PrelauncherEnabled",
MacUtil::CheckPrelauncherLoginItemStatus());
#endif // OS_MACOSX
LoadStats(&uploader);
// Just check for confirming that we can insert the value to upload_key.
if (!storage::Registry::Insert(upload_key, last_upload_sec)) {
LOG(ERROR) << "cannot save to registry";
return false;
}
if (!uploader.Upload()) {
LOG(ERROR) << "usagestats upload failed";
UsageStats::IncrementCount("UsageStatsUploadFailed");
return false;
}
UsageStats::ClearStats();
// Actual insersion to upload_key
if (!storage::Registry::Insert(upload_key, current_sec)) {
// This case is the worst case, but we will not send the data
// at the next trial, because next checking for insertion to upload_key
// should be failed.
LOG(ERROR) << "cannot save current_time to registry";
return false;
}
UsageStats::Sync();
VLOG(2) << "send success";
return true;
}
} // namespace usage_stats
} // namespace mozc