1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "usage_stats/usage_stats.h"
31
32 #include <algorithm>
33 #include <numeric>
34
35 #include "base/logging.h"
36 #include "config/stats_config_util.h"
37 #include "storage/registry.h"
38 #include "usage_stats/usage_stats.pb.h"
39
40 namespace mozc {
41 namespace usage_stats {
42
43 namespace {
44 const char kRegistryPrefix[] = "usage_stats.";
45
46 #include "usage_stats/usage_stats_list.h"
47
AddDoubleValueStats(const Stats::DoubleValueStats & src,Stats::DoubleValueStats * dest)48 void AddDoubleValueStats(
49 const Stats::DoubleValueStats &src,
50 Stats::DoubleValueStats *dest) {
51 dest->set_num(src.num() + dest->num());
52 dest->set_total(src.total() + dest->total());
53 dest->set_square_total(src.square_total() + dest->square_total());
54 }
55
AddTouchEventStats(const Stats::TouchEventStats & src_stats,Stats::TouchEventStats * dest_stats)56 void AddTouchEventStats(
57 const Stats::TouchEventStats &src_stats,
58 Stats::TouchEventStats *dest_stats) {
59 dest_stats->set_source_id(src_stats.source_id());
60 AddDoubleValueStats(src_stats.start_x_stats(),
61 dest_stats->mutable_start_x_stats());
62 AddDoubleValueStats(src_stats.start_y_stats(),
63 dest_stats->mutable_start_y_stats());
64 AddDoubleValueStats(src_stats.direction_x_stats(),
65 dest_stats->mutable_direction_x_stats());
66 AddDoubleValueStats(src_stats.direction_y_stats(),
67 dest_stats->mutable_direction_y_stats());
68 AddDoubleValueStats(src_stats.time_length_stats(),
69 dest_stats->mutable_time_length_stats());
70 }
71
LoadStats(const string & name,Stats * stats)72 bool LoadStats(const string &name, Stats *stats) {
73 DCHECK(UsageStats::IsListed(name)) << name << " is not in the list";
74 string stats_str;
75 const string key = kRegistryPrefix + name;
76 if (!mozc::storage::Registry::Lookup(key, &stats_str)) {
77 VLOG(1) << "Usage stats " << name << " is not registered yet.";
78 return false;
79 }
80 if (!stats->ParseFromString(stats_str)) {
81 LOG(ERROR) << "Parse error";
82 return false;
83 }
84 return true;
85 }
86
GetterInternal(const string & name,Stats::Type type,Stats * stats)87 bool GetterInternal(const string &name, Stats::Type type, Stats *stats) {
88 if (!LoadStats(name, stats)) {
89 return false;
90 }
91 if (stats->type() != type) {
92 LOG(ERROR) << "Type of " << name << " is not " << type
93 << " but " << stats->type() << ".";
94 return false;
95 }
96 return true;
97 }
98
SetterInternal(const string & name,const Stats & stats)99 bool SetterInternal(const string &name, const Stats &stats) {
100 const string key = kRegistryPrefix + name;
101 const string stats_str = stats.SerializeAsString();
102 if (!storage::Registry::Insert(key, stats_str)) {
103 LOG(ERROR) << "cannot save " << name << " to registry";
104 return false;
105 }
106 return true;
107 }
108 } // namespace
109
IsListed(const string & name)110 bool UsageStats::IsListed(const string &name) {
111 for (size_t i = 0; i < arraysize(kStatsList); ++i) {
112 if (name == kStatsList[i]) {
113 return true;
114 }
115 }
116 return false;
117 }
118
ClearStats()119 void UsageStats::ClearStats() {
120 string stats_str;
121 Stats stats;
122 for (size_t i = 0; i < arraysize(kStatsList); ++i) {
123 const string key = string(kRegistryPrefix) + kStatsList[i];
124 if (storage::Registry::Lookup(key, &stats_str)) {
125 if (!stats.ParseFromString(stats_str)) {
126 storage::Registry::Erase(key);
127 }
128 if (stats.type() == Stats::INTEGER ||
129 stats.type() == Stats::BOOLEAN) {
130 // We do not clear integer/boolean stats.
131 // These stats do not accumulate.
132 // We want send these stats at the next time
133 // even if they are not updated.
134 continue;
135 }
136 storage::Registry::Erase(key);
137 }
138 }
139 }
140
ClearAllStatsForTest()141 void UsageStats::ClearAllStatsForTest() {
142 for (size_t i = 0; i < arraysize(kStatsList); ++i) {
143 const string key = string(kRegistryPrefix) + kStatsList[i];
144 storage::Registry::Erase(key);
145 }
146 }
147
IncrementCountBy(const string & name,uint32 val)148 void UsageStats::IncrementCountBy(const string &name, uint32 val) {
149 DCHECK(IsListed(name)) << name << " is not in the list";
150 if (!config::StatsConfigUtil::IsEnabled()) {
151 return;
152 }
153
154 Stats stats;
155 if (GetterInternal(name, Stats::COUNT, &stats)) {
156 stats.set_count(stats.count() + val);
157 } else {
158 stats.set_name(name);
159 stats.set_type(Stats::COUNT);
160 stats.set_count(val);
161 }
162
163 SetterInternal(name, stats);
164 }
165
UpdateTiming(const string & name,uint32 val)166 void UsageStats::UpdateTiming(const string &name, uint32 val) {
167 DCHECK(IsListed(name)) << name << " is not in the list";
168 if (!config::StatsConfigUtil::IsEnabled()) {
169 return;
170 }
171
172 Stats stats;
173 if (GetterInternal(name, Stats::TIMING, &stats)) {
174 stats.set_num_timings(stats.num_timings() + 1);
175 stats.set_total_time(stats.total_time() + val);
176 stats.set_avg_time(stats.total_time() / stats.num_timings());
177 stats.set_min_time(std::min(stats.min_time(), val));
178 stats.set_max_time(std::max(stats.max_time(), val));
179 } else {
180 stats.set_name(name);
181 stats.set_type(Stats::TIMING);
182 stats.set_num_timings(1);
183 stats.set_total_time(val);
184 stats.set_avg_time(val);
185 stats.set_min_time(val);
186 stats.set_max_time(val);
187 }
188
189 SetterInternal(name, stats);
190 }
191
SetInteger(const string & name,int val)192 void UsageStats::SetInteger(const string &name, int val) {
193 DCHECK(IsListed(name)) << name << " is not in the list";
194 if (!config::StatsConfigUtil::IsEnabled()) {
195 return;
196 }
197
198 Stats stats;
199 stats.set_name(name);
200 stats.set_type(Stats::INTEGER);
201 stats.set_int_value(val);
202
203 SetterInternal(name, stats);
204 }
205
SetBoolean(const string & name,bool val)206 void UsageStats::SetBoolean(const string &name, bool val) {
207 DCHECK(IsListed(name)) << name << " is not in the list";
208 if (!config::StatsConfigUtil::IsEnabled()) {
209 return;
210 }
211
212 Stats stats;
213 stats.set_name(name);
214 stats.set_type(Stats::BOOLEAN);
215 stats.set_boolean_value(val);
216
217 SetterInternal(name, stats);
218 }
219
GetCountForTest(const string & name,uint32 * value)220 bool UsageStats::GetCountForTest(const string &name, uint32 *value) {
221 CHECK(value != NULL);
222 Stats stats;
223 if (!GetterInternal(name, Stats::COUNT, &stats)) {
224 return false;
225 }
226 if (!stats.has_count()) {
227 LOG(WARNING) << name << " has no counts.";
228 return false;
229 }
230
231 *value = stats.count();
232 return true;
233 }
234
GetIntegerForTest(const string & name,int32 * value)235 bool UsageStats::GetIntegerForTest(const string &name, int32 *value) {
236 CHECK(value != NULL);
237 Stats stats;
238 if (!GetterInternal(name, Stats::INTEGER, &stats)) {
239 return false;
240 }
241 if (!stats.has_int_value()) {
242 LOG(WARNING) << name << " has no integer values.";
243 return false;
244 }
245
246 *value = stats.int_value();
247 return true;
248 }
249
GetBooleanForTest(const string & name,bool * value)250 bool UsageStats::GetBooleanForTest(const string &name, bool *value) {
251 CHECK(value != NULL);
252 Stats stats;
253 if (!GetterInternal(name, Stats::BOOLEAN, &stats)) {
254 return false;
255 }
256 if (!stats.has_boolean_value()) {
257 LOG(WARNING) << name << " has no boolean values.";
258 return false;
259 }
260
261 *value = stats.boolean_value();
262 return true;
263 }
264
GetTimingForTest(const string & name,uint64 * total_time,uint32 * num_timings,uint32 * avg_time,uint32 * min_time,uint32 * max_time)265 bool UsageStats::GetTimingForTest(const string &name,
266 uint64 *total_time,
267 uint32 *num_timings,
268 uint32 *avg_time,
269 uint32 *min_time,
270 uint32 *max_time) {
271 Stats stats;
272 if (!GetterInternal(name, Stats::TIMING, &stats)) {
273 return false;
274 }
275
276 if ((total_time != NULL && !stats.has_total_time()) ||
277 (num_timings != NULL && !stats.has_num_timings()) ||
278 (avg_time != NULL && !stats.has_avg_time()) ||
279 (min_time != NULL && !stats.has_min_time()) ||
280 (max_time != NULL && !stats.has_max_time())) {
281 LOG(WARNING) << "cannot import stats of " << name << ".";
282 return false;
283 }
284
285 if (total_time != NULL) {
286 *total_time = stats.total_time();
287 }
288 if (num_timings != NULL) {
289 *num_timings = stats.num_timings();
290 }
291 if (avg_time != NULL) {
292 *avg_time = stats.avg_time();
293 }
294 if (min_time != NULL) {
295 *min_time = stats.min_time();
296 }
297 if (max_time != NULL) {
298 *max_time = stats.max_time();
299 }
300
301 return true;
302 }
303
GetVirtualKeyboardForTest(const string & name,Stats * stats)304 bool UsageStats::GetVirtualKeyboardForTest(const string &name, Stats *stats) {
305 if (!GetterInternal(name, Stats::VIRTUAL_KEYBOARD, stats)) {
306 return false;
307 }
308
309 if (stats->virtual_keyboard_stats_size() == 0) {
310 LOG(WARNING) << name << " has no virtual keyboard values.";
311 stats->Clear();
312 return false;
313 }
314
315 return true;
316 }
317
GetStatsForTest(const string & name,Stats * stats)318 bool UsageStats::GetStatsForTest(const string &name, Stats *stats) {
319 return LoadStats(name, stats);
320 }
321
StoreTouchEventStats(const string & name,const std::map<string,TouchEventStatsMap> & touch_stats)322 void UsageStats::StoreTouchEventStats(
323 const string &name,
324 const std::map<string, TouchEventStatsMap> &touch_stats) {
325 DCHECK(IsListed(name)) << name << " is not in the list";
326 if (touch_stats.empty()) {
327 return;
328 }
329
330 Stats stats;
331 std::map<string, TouchEventStatsMap> tmp_stats(touch_stats);
332 if (GetterInternal(name, Stats::VIRTUAL_KEYBOARD, &stats)) {
333 for (size_t i = 0; i < stats.virtual_keyboard_stats_size(); ++i) {
334 const Stats::VirtualKeyboardStats &virtual_keyboard_stats =
335 stats.virtual_keyboard_stats(i);
336 const string &keyboard_name = virtual_keyboard_stats.keyboard_name();
337 TouchEventStatsMap &stats_map = tmp_stats[keyboard_name];
338
339 for (size_t j = 0; j < virtual_keyboard_stats.touch_event_stats_size();
340 ++j) {
341 const Stats::TouchEventStats &src_stats =
342 virtual_keyboard_stats.touch_event_stats(j);
343 Stats::TouchEventStats &dest_stats = stats_map[src_stats.source_id()];
344 AddTouchEventStats(src_stats, &dest_stats);
345 }
346 }
347 } else {
348 stats.set_name(name);
349 stats.set_type(Stats::VIRTUAL_KEYBOARD);
350 }
351
352 stats.clear_virtual_keyboard_stats();
353 for (std::map<string, TouchEventStatsMap>::const_iterator iter =
354 tmp_stats.begin();
355 iter != tmp_stats.end(); ++iter) {
356 Stats::VirtualKeyboardStats *virtual_keyboard_stats =
357 stats.add_virtual_keyboard_stats();
358 virtual_keyboard_stats->set_keyboard_name(iter->first);
359 for (TouchEventStatsMap::const_iterator it = iter->second.begin();
360 it != iter->second.end(); ++it) {
361 Stats::TouchEventStats *touch_event_stats =
362 virtual_keyboard_stats->add_touch_event_stats();
363 touch_event_stats->CopyFrom(it->second);
364 }
365 }
366
367 SetterInternal(name, stats);
368 }
369
Sync()370 bool UsageStats::Sync() {
371 if (!storage::Registry::Sync()) {
372 LOG(ERROR) << "sync failed";
373 return false;
374 }
375 return true;
376 }
377
378 } // namespace usage_stats
379 } // namespace mozc
380