| // 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 ¤t_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 |