blob: 88782e8b1c8bd01b500ff5c120a2e4edaef1dacf [file] [log] [blame]
// Copyright 2010-2015, 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 <utility>
#include <vector>
#ifdef OS_ANDROID
#include "base/android_util.h"
#endif // OS_ANDROID
#include "base/encryptor.h"
#include "base/mutex.h"
#include "base/port.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/version.h"
#include "config/stats_config_util.h"
#include "storage/registry.h"
#include "usage_stats/upload_util.h"
#include "usage_stats/usage_stats.h"
#include "usage_stats/usage_stats.pb.h"
#include "usage_stats/usage_stats_updater.h"
namespace mozc {
namespace usage_stats {
namespace {
const char kRegistryPrefix[] = "usage_stats.";
const char kLastUploadKey[] = "last_upload";
const char kMozcVersionKey[] = "mozc_version";
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::GetRandomAsciiSequence(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; // NOLINT
ClientIdInterface &GetClientIdHandler() {
scoped_lock l(&g_mutex);
if (g_client_id_handler == NULL) {
return *(Singleton<ClientIdImpl>::get());
} else {
return *g_client_id_handler;
}
}
void AddDoubleValueStatsToUploadUtil(
const string &key_name_base,
const Stats::DoubleValueStats &double_stats,
double average_scale, double variance_scale,
UploadUtil *uploader) {
if (double_stats.num() == 0) {
return;
}
double average = double_stats.total() / double_stats.num();
double variance =
double_stats.square_total() / double_stats.num() - average * average;
uploader->AddIntegerValue(key_name_base + "a",
static_cast<int>(average * average_scale));
uploader->AddIntegerValue(key_name_base + "v",
static_cast<int>(variance * variance_scale));
}
void AddVirtualKeyboardStatsToUploadUtil(const Stats &stats,
UploadUtil *uploader) {
DCHECK(stats.type() == Stats::VIRTUAL_KEYBOARD);
string stats_name = stats.name();
// Change stats name to reduce network traffic
if (stats_name == "VirtualKeyboardStats") {
stats_name = "vks";
} else if (stats_name == "VirtualKeyboardMissStats") {
stats_name = "vkms";
} else {
LOG(ERROR) << "Unexpected stats_name: " << stats_name;
return;
}
for (size_t i = 0; i < stats.virtual_keyboard_stats_size(); ++i) {
const Stats::VirtualKeyboardStats &virtual_keyboard_stats =
stats.virtual_keyboard_stats(i);
// Set the keyboard_id
// example:
// vks_name_TWELVE_KEY_TOGGLE_FLICK_KANA : 0
// vks_name_TWELVE_KEY_TOGGLE_KANA : 1
// vks_name_TWELVE_KEY_TOGGLE_NUMBER : 2
// vkms_name_TWELVE_KEY_TOGGLE_FLICK_KANA : 0
// vkms_name_TWELVE_KEY_TOGGLE_NUMBER : 1
uploader->AddIntegerValue(stats_name + "_name_" +
virtual_keyboard_stats.keyboard_name(),
i);
// Set the average and the variance of each stat.
// example:
// vks_1_3_sxa (VirtualKeyboardStats_StartXAverage_keyboard1_sourceid3)
// ^^^ | | ||| : vks(VirtualKeyboardStats), vkms(VirtualKeyboardMissStats)
// ^ | ||| : keyboad_id
// ^ ||| : source_id
// ^^| : sx(StartX), sx(StartY), dx(DirectionX), dy(DirectionY),
// | tl(TimeLength)
// ^ : a(Average), v(Variance)
for (size_t j = 0; j < virtual_keyboard_stats.touch_event_stats_size();
++j) {
// Calculate average and variance
// Average = total / num
// Variance = square_total / num - (total / num) ^ 2
// Because the current log analysis system can only deal with int values,
// we multiply these values by a scale factor and send them to server.
// sxa, sya, dxa, dya : scale = 10000000
// sxv, syv, dxv, dyv : scale = 10000000
// tla, tlv : scale = 10000000
const Stats::TouchEventStats &touch =
virtual_keyboard_stats.touch_event_stats(j);
const string key_name_base = Util::StringPrintf(
"%s_%d_%d_", stats_name.c_str(), static_cast<int>(i),
touch.source_id());
AddDoubleValueStatsToUploadUtil(key_name_base + "sx",
touch.start_x_stats(),
10000000.0, 10000000.0,
uploader);
AddDoubleValueStatsToUploadUtil(key_name_base + "sy",
touch.start_y_stats(),
10000000.0, 10000000.0,
uploader);
AddDoubleValueStatsToUploadUtil(key_name_base + "dx",
touch.direction_x_stats(),
10000000.0, 10000000.0,
uploader);
AddDoubleValueStatsToUploadUtil(key_name_base + "dy",
touch.direction_y_stats(),
10000000.0, 10000000.0,
uploader);
AddDoubleValueStatsToUploadUtil(key_name_base + "tl",
touch.time_length_stats(),
10000000.0, 10000000.0,
uploader);
}
}
}
} // namespace
ClientIdInterface::ClientIdInterface() {}
ClientIdInterface::~ClientIdInterface() {}
// 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;
case Stats::VIRTUAL_KEYBOARD:
AddVirtualKeyboardStatsToUploadUtil(stats, uploader);
break;
default:
VLOG(3) << "stats " << name << " has no type";
break;
}
}
}
void UsageStatsUploader::GetClientId(string *output) {
GetClientIdHandler().GetClientId(output);
}
bool UsageStatsUploader::Send(void *data) {
const string upload_key = string(kRegistryPrefix) + kLastUploadKey;
const uint32 current_sec = static_cast<uint32>(Util::GetTime());
uint32 last_upload_sec = 0;
const string mozc_version_key = string(kRegistryPrefix) + kMozcVersionKey;
const string &current_mozc_version = Version::GetMozcVersion();
string last_mozc_version;
if (!storage::Registry::Lookup(upload_key, &last_upload_sec) ||
last_upload_sec > current_sec ||
!storage::Registry::Lookup(mozc_version_key, &last_mozc_version) ||
last_mozc_version != current_mozc_version) {
// quit here just saving current time and clear stats
UsageStats::ClearStats();
bool result = true;
result &= storage::Registry::Insert(upload_key, current_sec);
result &= storage::Registry::Insert(mozc_version_key, current_mozc_version);
LOG_IF(ERROR, !result) << "cannot save usage stats metadata to registry";
return result;
}
// if usage stats is disabled, we simply clear stats here.
if (!mozc::config::StatsConfigUtil::IsEnabled()) {
UsageStats::ClearStats();
VLOG(1) << "UsageStats is disabled.";
return false;
}
uint32 elapsed_sec = 0;
if (current_sec >= last_upload_sec) {
elapsed_sec = current_sec - last_upload_sec;
} else {
LOG(WARNING) << "Timing counter seems to be reversed."
<< " current_sec: " << current_sec
<< " last_upload_sec: " << last_upload_sec;
}
if (elapsed_sec < kSendInterval) {
VLOG(1) << "Skip uploading the data as not enough time has passed yet."
<< " current_sec: " << current_sec
<< " last_upload_sec: " << last_upload_sec
<< " elapsed_sec: " << elapsed_sec
<< " kSendInterval: " << kSendInterval;
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", SystemUtil::GetOSVersionString()));
#ifdef OS_ANDROID
params.push_back(
make_pair("model",
AndroidUtil::GetSystemProperty(
AndroidUtil::kSystemPropertyModel, "Unknown")));
#endif // OS_ANDROID
UsageStatsUpdater::UpdateStats();
UploadUtil uploader;
uploader.SetHeader("Daily", elapsed_sec, params);
#ifdef __native_client__
// In NaCl Mozc we use HTTPS to send usage stats to follow Chrome OS
// convention. https://code.google.com/p/chromium/issues/detail?id=255327
uploader.SetUseHttps(true);
#endif // __native_client__
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;
}
if (!UsageStats::Sync()) {
LOG(ERROR) << "Failed to sync cleared usage stats to disk storage.";
return false;
}
VLOG(2) << "send success";
return true;
}
} // namespace usage_stats
} // namespace mozc