Codebase list mozc / debian/1.15.1857.102-1ubuntu1 usage_stats / usage_stats_uploader.cc
debian/1.15.1857.102-1ubuntu1

Tree @debian/1.15.1857.102-1ubuntu1 (Download .tar.gz)

usage_stats_uploader.cc @debian/1.15.1857.102-1ubuntu1raw · history · blame

// Copyright 2010-2014, 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