blob: cb60257991b79ffd7a36a60fd5970fdb815e0692 [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 <algorithm>
#include <cmath>
#include <iostream> // NOLINT
#include <iterator>
#include <vector>
#include <string>
#include "base/file_stream.h"
#include "base/flags.h"
#include "base/logging.h"
#include "base/port.h"
#include "base/singleton.h"
#include "base/stopwatch.h"
#include "base/util.h"
#include "client/client.h"
#include "config/config_handler.h"
#include "session/commands.pb.h"
#include "session/random_keyevents_generator.h"
DEFINE_string(server_path, "", "specify server path");
DEFINE_string(log_path, "", "specify log output file path");
namespace mozc {
namespace {
struct Result {
string test_name;
vector<uint32> operations_times;
};
class TestSentenceGenerator {
public:
const vector<vector<commands::KeyEvent> > &GetTestKeys() const {
return keys_;
}
TestSentenceGenerator() {
size_t size = 0;
const char **sentences =
session::RandomKeyEventsGenerator::GetTestSentences(&size);
CHECK_GT(size, 0);
size = min(static_cast<size_t>(200), size);
for (size_t i = 0; i < size; ++i) {
string output;
Util::HiraganaToRomanji(sentences[i], &output);
vector<commands::KeyEvent> tmp;
for (ConstChar32Iterator iter(output); !iter.Done(); iter.Next()) {
const char32 ucs4 = iter.Get();
if (ucs4 >= static_cast<char32>('a') &&
ucs4 <= static_cast<char32>('z')) {
commands::KeyEvent key;
key.set_key_code(static_cast<int>(ucs4));
tmp.push_back(key);
}
}
if (!tmp.empty()) {
keys_.push_back(tmp);
}
}
}
private:
vector<vector<commands::KeyEvent> > keys_;
};
class TestScenarioInterface {
public:
virtual void Run(Result *result) = 0;
TestScenarioInterface() {
if (!FLAGS_server_path.empty()) {
client_.set_server_program(FLAGS_server_path);
}
CHECK(client_.IsValidRunLevel()) << "IsValidRunLevel failed";
CHECK(client_.EnsureSession()) << "EnsureSession failed";
CHECK(client_.NoOperation()) << "Server is not responding";
}
virtual ~TestScenarioInterface() {}
protected:
virtual void IMEOn() {
commands::KeyEvent key;
key.set_special_key(commands::KeyEvent::ON);
client_.SendKey(key, &output_);
}
virtual void IMEOff() {
commands::KeyEvent key;
key.set_special_key(commands::KeyEvent::OFF);
client_.SendKey(key, &output_);
}
virtual void ResetConfig() {
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
client_.SetConfig(config);
}
virtual void EnableSuggestion() {
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
config.set_use_history_suggest(true);
config.set_use_dictionary_suggest(true);
client_.SetConfig(config);
}
virtual void DisableSuggestion() {
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
config.set_use_history_suggest(false);
config.set_use_dictionary_suggest(false);
client_.SetConfig(config);
}
client::Client client_;
commands::Output output_;
};
string GetBasicStats(const vector<uint32> times) {
uint32 total_time = 0;
uint32 avg_time = 0;
uint32 max_time = 0;
uint32 min_time = 0;
uint32 sd_time = 0; // Standard Deviation
uint32 med_time = 0;
min_time = INT_MAX;
max_time = 0;
for (size_t i = 0; i < times.size(); ++i) {
total_time += times[i];
min_time = min(times[i], min_time);
max_time = max(times[i], max_time);
}
avg_time = static_cast<uint32>(1.0 * total_time / times.size());
if (times.size() >= 2) {
double dsd_time = 0;
for (size_t i = 0; i < times.size(); ++i) {
dsd_time += (avg_time - times[i]) * (avg_time - times[i]);
}
dsd_time = sqrt(dsd_time / (times.size() - 1));
sd_time = static_cast<uint32>(dsd_time);
}
if (!times.empty()) {
vector<uint32> tmp(times);
sort(tmp.begin(), tmp.end());
med_time = tmp[tmp.size() / 2];
}
return Util::StringPrintf(
"size=%d total=%d avg=%d max=%d min=%d st=%d med=%d",
static_cast<int>(times.size()),
total_time,
avg_time,
max_time,
min_time,
sd_time,
med_time);
}
class PreeditCommon : public TestScenarioInterface {
protected:
virtual void RunTest(Result *result) {
const vector<vector<commands::KeyEvent> > &keys =
Singleton<TestSentenceGenerator>::get()->GetTestKeys();
for (size_t i = 0; i < keys.size(); ++i) {
for (int j = 0; j < keys[i].size(); ++j) {
Stopwatch stopwatch;
stopwatch.Start();
client_.SendKey(keys[i][j], &output_);
stopwatch.Stop();
result->operations_times.push_back(stopwatch.GetElapsedMicroseconds());
}
commands::SessionCommand command;
command.set_type(commands::SessionCommand::REVERT);
client_.SendCommand(command, &output_);
}
}
};
class PreeditWithoutSuggestion : public PreeditCommon {
public:
virtual void Run(Result *result) {
result->test_name = "preedit_without_suggestion";
ResetConfig();
IMEOn();
DisableSuggestion();
RunTest(result);
IMEOff();
ResetConfig();
}
};
class PreeditWithSuggestion : public PreeditCommon {
public:
virtual void Run(Result *result) {
result->test_name = "preedit_with_suggestion";
ResetConfig();
IMEOn();
EnableSuggestion();
RunTest(result);
IMEOff();
ResetConfig();
}
};
enum PredictionRequestType {
ONE_CHAR,
TWO_CHARS
};
void CreatePredictionKeys(PredictionRequestType type,
vector <string> *request_keys) {
CHECK(request_keys);
request_keys->clear();
const char *kVoels[] = { "a", "i", "u", "e", "o" };
const char *kConsonant[] = { "k", "s", "t", "n", "h",
"m", "y", "r", "w" };
vector<string> one_chars;
for (size_t i = 0; i < arraysize(kVoels); ++i) {
one_chars.push_back(kVoels[i]);
}
for (size_t i = 0; i < arraysize(kConsonant); ++i) {
for (size_t j = 0; j < arraysize(kVoels); ++j) {
one_chars.push_back(string(kConsonant[i]) + string(kVoels[j]));
}
}
vector<string> two_chars;
for (size_t i = 0; i < one_chars.size(); ++i) {
for (size_t j = 0; j < one_chars.size(); ++j) {
two_chars.push_back(one_chars[i] + one_chars[j]);
}
}
switch (type) {
case ONE_CHAR:
copy(one_chars.begin(), one_chars.end(),
back_inserter(*request_keys));
break;
case TWO_CHARS:
copy(two_chars.begin(), two_chars.end(),
back_inserter(*request_keys));
break;
default:
break;
}
CHECK(!request_keys->empty());
}
class PredictionCommon: public TestScenarioInterface {
protected:
void RunTest(PredictionRequestType type, Result *result) {
IMEOn();
ResetConfig();
DisableSuggestion();
vector<string> request_keys;
CreatePredictionKeys(type, &request_keys);
for (size_t i = 0; i < request_keys.size(); ++i) {
const string &keys = request_keys[i];
for (size_t j = 0; j < keys.size(); ++j) {
commands::KeyEvent key;
key.set_key_code(static_cast<int>(keys[j]));
client_.SendKey(key, &output_);
}
commands::KeyEvent key;
key.set_special_key(commands::KeyEvent::TAB);
Stopwatch stopwatch;
stopwatch.Start();
client_.SendKey(key, &output_);
stopwatch.Stop();
result->operations_times.push_back(stopwatch.GetElapsedMicroseconds());
commands::SessionCommand command;
command.set_type(commands::SessionCommand::REVERT);
client_.SendCommand(command, &output_);
}
IMEOff();
}
};
class PredictionWithOneChar : public PredictionCommon {
public:
virtual void Run(Result *result) {
result->test_name = "prediction_one_char";
RunTest(ONE_CHAR, result);
}
};
class PredictionWithTwoChars : public PredictionCommon {
public:
virtual void Run(Result *result) {
result->test_name = "prediction_two_chars";
RunTest(TWO_CHARS, result);
}
};
class Conversion : public TestScenarioInterface {
public:
virtual void Run(Result *result) {
result->test_name = "conversion";
ResetConfig();
DisableSuggestion();
IMEOn();
const vector<vector<commands::KeyEvent> > &keys =
Singleton<TestSentenceGenerator>::get()->GetTestKeys();
for (size_t i = 0; i < keys.size(); ++i) {
for (int j = 0; j < keys[i].size(); ++j) {
client_.SendKey(keys[i][j], &output_);
}
commands::KeyEvent key;
key.set_special_key(commands::KeyEvent::SPACE);
Stopwatch stopwatch;
stopwatch.Start();
client_.SendKey(key, &output_);
stopwatch.Stop();
result->operations_times.push_back(stopwatch.GetElapsedMicroseconds());
commands::SessionCommand command;
command.set_type(commands::SessionCommand::REVERT);
client_.SendCommand(command, &output_);
}
IMEOff();
ResetConfig();
}
};
} // namespace
} // namespace mozc
int main(int argc, char **argv) {
InitGoogle(argv[0], &argc, &argv, false);
vector<mozc::TestScenarioInterface *> tests;
vector<mozc::Result *> results;
tests.push_back(new mozc::PreeditWithoutSuggestion);
tests.push_back(new mozc::PreeditWithSuggestion);
tests.push_back(new mozc::Conversion);
tests.push_back(new mozc::PredictionWithOneChar);
tests.push_back(new mozc::PredictionWithTwoChars);
for (size_t i = 0; i < tests.size(); ++i) {
mozc::Result *result = new mozc::Result;
tests[i]->Run(result);
results.push_back(result);
}
CHECK_EQ(results.size(), tests.size());
ostream *ofs = &cout;
if (!FLAGS_log_path.empty()) {
ofs = new mozc::OutputFileStream(FLAGS_log_path.c_str());
}
// TODO(taku): generate histogram with ChartAPI
for (size_t i = 0; i < tests.size(); ++i) {
(*ofs) << results[i]->test_name << ": " <<
mozc::GetBasicStats(results[i]->operations_times) << endl;
delete tests[i];
delete results[i];
}
if (ofs != &cout) {
delete ofs;
}
return 0;
}