Codebase list mozc / upstream/latest usage_stats / usage_stats.cc
upstream/latest

Tree @upstream/latest (Download .tar.gz)

usage_stats.cc @upstream/latestraw · 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.h"

#include <algorithm>
#include <numeric>

#include "base/logging.h"
#include "config/stats_config_util.h"
#include "storage/registry.h"
#include "usage_stats/usage_stats.pb.h"

namespace mozc {
namespace usage_stats {

namespace {
const char kRegistryPrefix[] = "usage_stats.";

#include "usage_stats/usage_stats_list.h"

void AddDoubleValueStats(
    const Stats::DoubleValueStats &src,
    Stats::DoubleValueStats *dest) {
  dest->set_num(src.num() + dest->num());
  dest->set_total(src.total() + dest->total());
  dest->set_square_total(src.square_total() + dest->square_total());
}

void AddTouchEventStats(
    const Stats::TouchEventStats &src_stats,
    Stats::TouchEventStats *dest_stats) {
  dest_stats->set_source_id(src_stats.source_id());
  AddDoubleValueStats(src_stats.start_x_stats(),
                      dest_stats->mutable_start_x_stats());
  AddDoubleValueStats(src_stats.start_y_stats(),
                      dest_stats->mutable_start_y_stats());
  AddDoubleValueStats(src_stats.direction_x_stats(),
                      dest_stats->mutable_direction_x_stats());
  AddDoubleValueStats(src_stats.direction_y_stats(),
                      dest_stats->mutable_direction_y_stats());
  AddDoubleValueStats(src_stats.time_length_stats(),
                      dest_stats->mutable_time_length_stats());
}

bool LoadStats(const string &name, Stats *stats) {
  DCHECK(UsageStats::IsListed(name)) << name << " is not in the list";
  string stats_str;
  const string key = kRegistryPrefix + name;
  if (!mozc::storage::Registry::Lookup(key, &stats_str)) {
    VLOG(1) << "Usage stats " << name << " is not registered yet.";
    return false;
  }
  if (!stats->ParseFromString(stats_str)) {
    LOG(ERROR) << "Parse error";
    return false;
  }
  return true;
}

bool GetterInternal(const string &name, Stats::Type type, Stats *stats) {
  if (!LoadStats(name, stats)) {
    return false;
  }
  if (stats->type() != type) {
    LOG(ERROR) << "Type of " << name << " is not " << type
               << " but " << stats->type() << ".";
    return false;
  }
  return true;
}

bool SetterInternal(const string &name, const Stats &stats) {
  const string key = kRegistryPrefix + name;
  const string stats_str = stats.SerializeAsString();
  if (!storage::Registry::Insert(key, stats_str)) {
    LOG(ERROR) << "cannot save " << name << " to registry";
    return false;
  }
  return true;
}
}  // namespace

bool UsageStats::IsListed(const string &name) {
  for (size_t i = 0; i < arraysize(kStatsList); ++i) {
    if (name == kStatsList[i]) {
      return true;
    }
  }
  return false;
}

void UsageStats::ClearStats() {
  string stats_str;
  Stats stats;
  for (size_t i = 0; i < arraysize(kStatsList); ++i) {
    const string key = string(kRegistryPrefix) + kStatsList[i];
    if (storage::Registry::Lookup(key, &stats_str)) {
      if (!stats.ParseFromString(stats_str)) {
        storage::Registry::Erase(key);
      }
      if (stats.type() == Stats::INTEGER ||
          stats.type() == Stats::BOOLEAN) {
        // We do not clear integer/boolean stats.
        // These stats do not accumulate.
        // We want send these stats at the next time
        // even if they are not updated.
        continue;
      }
      storage::Registry::Erase(key);
    }
  }
}

void UsageStats::ClearAllStatsForTest() {
  for (size_t i = 0; i < arraysize(kStatsList); ++i) {
    const string key = string(kRegistryPrefix) + kStatsList[i];
    storage::Registry::Erase(key);
  }
}

void UsageStats::IncrementCountBy(const string &name, uint32 val) {
  DCHECK(IsListed(name)) << name << " is not in the list";
  if (!config::StatsConfigUtil::IsEnabled()) {
    return;
  }

  Stats stats;
  if (GetterInternal(name, Stats::COUNT, &stats)) {
    stats.set_count(stats.count() + val);
  } else {
    stats.set_name(name);
    stats.set_type(Stats::COUNT);
    stats.set_count(val);
  }

  SetterInternal(name, stats);
}

void UsageStats::UpdateTiming(const string &name, uint32 val) {
  DCHECK(IsListed(name)) << name << " is not in the list";
  if (!config::StatsConfigUtil::IsEnabled()) {
    return;
  }

  Stats stats;
  if (GetterInternal(name, Stats::TIMING, &stats)) {
    stats.set_num_timings(stats.num_timings() + 1);
    stats.set_total_time(stats.total_time() + val);
    stats.set_avg_time(stats.total_time() / stats.num_timings());
    stats.set_min_time(min(stats.min_time(), val));
    stats.set_max_time(max(stats.max_time(), val));
  } else {
    stats.set_name(name);
    stats.set_type(Stats::TIMING);
    stats.set_num_timings(1);
    stats.set_total_time(val);
    stats.set_avg_time(val);
    stats.set_min_time(val);
    stats.set_max_time(val);
  }

  SetterInternal(name, stats);
}

void UsageStats::SetInteger(const string &name, int val) {
  DCHECK(IsListed(name)) << name << " is not in the list";
  if (!config::StatsConfigUtil::IsEnabled()) {
    return;
  }

  Stats stats;
  stats.set_name(name);
  stats.set_type(Stats::INTEGER);
  stats.set_int_value(val);

  SetterInternal(name, stats);
}

void UsageStats::SetBoolean(const string &name, bool val) {
  DCHECK(IsListed(name)) << name << " is not in the list";
  if (!config::StatsConfigUtil::IsEnabled()) {
    return;
  }

  Stats stats;
  stats.set_name(name);
  stats.set_type(Stats::BOOLEAN);
  stats.set_boolean_value(val);

  SetterInternal(name, stats);
}

bool UsageStats::GetCountForTest(const string &name, uint32 *value) {
  CHECK(value != NULL);
  Stats stats;
  if (!GetterInternal(name, Stats::COUNT, &stats)) {
    return false;
  }
  if (!stats.has_count()) {
    LOG(WARNING) << name << " has no counts.";
    return false;
  }

  *value = stats.count();
  return true;
}

bool UsageStats::GetIntegerForTest(const string &name, int32 *value) {
  CHECK(value != NULL);
  Stats stats;
  if (!GetterInternal(name, Stats::INTEGER, &stats)) {
    return false;
  }
  if (!stats.has_int_value()) {
    LOG(WARNING) << name << " has no integer values.";
    return false;
  }

  *value = stats.int_value();
  return true;
}

bool UsageStats::GetBooleanForTest(const string &name, bool *value) {
  CHECK(value != NULL);
  Stats stats;
  if (!GetterInternal(name, Stats::BOOLEAN, &stats)) {
    return false;
  }
  if (!stats.has_boolean_value()) {
    LOG(WARNING) << name << " has no boolean values.";
    return false;
  }

  *value = stats.boolean_value();
  return true;
}

bool UsageStats::GetTimingForTest(const string &name,
                                  uint64 *total_time,
                                  uint32 *num_timings,
                                  uint32 *avg_time,
                                  uint32 *min_time,
                                  uint32 *max_time) {
  Stats stats;
  if (!GetterInternal(name, Stats::TIMING, &stats)) {
    return false;
  }

  if ((total_time != NULL && !stats.has_total_time()) ||
      (num_timings != NULL && !stats.has_num_timings()) ||
      (avg_time != NULL && !stats.has_avg_time()) ||
      (min_time != NULL && !stats.has_min_time()) ||
      (max_time != NULL && !stats.has_max_time())) {
    LOG(WARNING) << "cannot import stats of " << name << ".";
    return false;
  }

  if (total_time != NULL) {
    *total_time = stats.total_time();
  }
  if (num_timings != NULL) {
    *num_timings = stats.num_timings();
  }
  if (avg_time != NULL) {
    *avg_time = stats.avg_time();
  }
  if (min_time != NULL) {
    *min_time = stats.min_time();
  }
  if (max_time != NULL) {
    *max_time = stats.max_time();
  }

  return true;
}

bool UsageStats::GetVirtualKeyboardForTest(const string &name, Stats *stats) {
  if (!GetterInternal(name, Stats::VIRTUAL_KEYBOARD, stats)) {
    return false;
  }

  if (stats->virtual_keyboard_stats_size() == 0) {
    LOG(WARNING) << name << " has no virtual keyboard values.";
    stats->Clear();
    return false;
  }

  return true;
}

bool UsageStats::GetStatsForTest(const string &name, Stats *stats) {
  return LoadStats(name, stats);
}

void UsageStats::StoreTouchEventStats(
    const string &name, const map<string, TouchEventStatsMap> &touch_stats) {
  DCHECK(IsListed(name)) << name << " is not in the list";
  if (touch_stats.empty()) {
    return;
  }

  Stats stats;
  map<string, TouchEventStatsMap> tmp_stats(touch_stats);
  if (GetterInternal(name, Stats::VIRTUAL_KEYBOARD, &stats)) {
    for (size_t i = 0; i < stats.virtual_keyboard_stats_size(); ++i) {
      const Stats::VirtualKeyboardStats &virtual_keyboard_stats =
          stats.virtual_keyboard_stats(i);
      const string &keyboard_name = virtual_keyboard_stats.keyboard_name();
      TouchEventStatsMap &stats_map = tmp_stats[keyboard_name];

      for (size_t j = 0; j < virtual_keyboard_stats.touch_event_stats_size();
          ++j) {
        const Stats::TouchEventStats &src_stats =
            virtual_keyboard_stats.touch_event_stats(j);
        Stats::TouchEventStats &dest_stats = stats_map[src_stats.source_id()];
        AddTouchEventStats(src_stats, &dest_stats);
      }
    }
  } else {
    stats.set_name(name);
    stats.set_type(Stats::VIRTUAL_KEYBOARD);
  }

  stats.clear_virtual_keyboard_stats();
  for (map<string, TouchEventStatsMap>::const_iterator iter =
           tmp_stats.begin();
       iter != tmp_stats.end(); ++iter) {
    Stats::VirtualKeyboardStats *virtual_keyboard_stats =
        stats.add_virtual_keyboard_stats();
    virtual_keyboard_stats->set_keyboard_name(iter->first);
    for (TouchEventStatsMap::const_iterator it = iter->second.begin();
        it != iter->second.end(); ++it) {
      Stats::TouchEventStats *touch_event_stats =
          virtual_keyboard_stats->add_touch_event_stats();
      touch_event_stats->CopyFrom(it->second);
    }
  }

  SetterInternal(name, stats);
}

bool UsageStats::Sync() {
  if (!storage::Registry::Sync()) {
    LOG(ERROR) << "sync failed";
    return false;
  }
  return true;
}

}  // namespace usage_stats
}  // namespace mozc