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