OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/metrics/persisted_logs.h" | 5 #include "components/metrics/persisted_logs.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/base64.h" | 9 #include "base/base64.h" |
10 #include "base/md5.h" | 10 #include "base/md5.h" |
11 #include "base/metrics/histogram.h" | 11 #include "base/metrics/histogram.h" |
12 #include "base/prefs/pref_service.h" | 12 #include "base/prefs/pref_service.h" |
13 #include "base/prefs/scoped_user_pref_update.h" | 13 #include "base/prefs/scoped_user_pref_update.h" |
14 #include "base/sha1.h" | 14 #include "base/sha1.h" |
15 #include "base/timer/elapsed_timer.h" | 15 #include "base/timer/elapsed_timer.h" |
| 16 #include "components/metrics/compression_utils.h" |
16 | 17 |
17 namespace metrics { | 18 namespace metrics { |
18 | 19 |
19 namespace { | 20 namespace { |
20 | 21 |
21 // We append (2) more elements to persisted lists: the size of the list and a | |
22 // checksum of the elements. | |
23 const size_t kChecksumEntryCount = 2; | |
24 | |
25 PersistedLogs::LogReadStatus MakeRecallStatusHistogram( | 22 PersistedLogs::LogReadStatus MakeRecallStatusHistogram( |
26 PersistedLogs::LogReadStatus status) { | 23 PersistedLogs::LogReadStatus status) { |
27 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", | 24 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", |
28 status, PersistedLogs::END_RECALL_STATUS); | 25 status, PersistedLogs::END_RECALL_STATUS); |
29 return status; | 26 return status; |
30 } | 27 } |
31 | 28 |
| 29 // Reads the value at |index| from |list_value| as a string and Base64-decodes |
| 30 // it into |result|. Returns true on success. |
| 31 bool ReadBase64String(const base::ListValue& list_value, |
| 32 size_t index, |
| 33 std::string* result) { |
| 34 std::string base64_result; |
| 35 if (!list_value.GetString(index, &base64_result)) |
| 36 return false; |
| 37 return base::Base64Decode(base64_result, result); |
| 38 } |
| 39 |
| 40 // Base64-encodes |str| and appends the result to |list_value|. |
| 41 void AppendBase64String(const std::string& str, base::ListValue* list_value) { |
| 42 std::string base64_str; |
| 43 base::Base64Encode(str, &base64_str); |
| 44 list_value->Append(base::Value::CreateStringValue(base64_str)); |
| 45 } |
| 46 |
32 } // namespace | 47 } // namespace |
33 | 48 |
34 void PersistedLogs::LogHashPair::SwapLog(std::string* input) { | 49 void PersistedLogs::LogHashPair::Init(const std::string& log_data) { |
35 log.swap(*input); | 50 DCHECK(!log_data.empty()); |
36 if (!log.empty()) | 51 |
37 hash = base::SHA1HashString(log); | 52 if (!GzipCompress(log_data, &compressed_log_data)) { |
38 else | 53 NOTREACHED(); |
39 hash.clear(); | 54 return; |
| 55 } |
| 56 |
| 57 UMA_HISTOGRAM_PERCENTAGE( |
| 58 "UMA.ProtoCompressionRatio", |
| 59 static_cast<int>(100 * compressed_log_data.size() / log_data.size())); |
| 60 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 61 "UMA.ProtoGzippedKBSaved", |
| 62 static_cast<int>((log_data.size() - compressed_log_data.size()) / 1024), |
| 63 1, 2000, 50); |
| 64 |
| 65 hash = base::SHA1HashString(log_data); |
| 66 } |
| 67 |
| 68 void PersistedLogs::LogHashPair::Clear() { |
| 69 compressed_log_data.clear(); |
| 70 hash.clear(); |
40 } | 71 } |
41 | 72 |
42 void PersistedLogs::LogHashPair::Swap(PersistedLogs::LogHashPair* input) { | 73 void PersistedLogs::LogHashPair::Swap(PersistedLogs::LogHashPair* input) { |
43 log.swap(input->log); | 74 compressed_log_data.swap(input->compressed_log_data); |
44 hash.swap(input->hash); | 75 hash.swap(input->hash); |
45 } | 76 } |
46 | 77 |
47 PersistedLogs::PersistedLogs(PrefService* local_state, | 78 PersistedLogs::PersistedLogs(PrefService* local_state, |
48 const char* pref_name, | 79 const char* pref_name, |
| 80 const char* old_pref_name, |
49 size_t min_log_count, | 81 size_t min_log_count, |
50 size_t min_log_bytes, | 82 size_t min_log_bytes, |
51 size_t max_log_size) | 83 size_t max_log_size) |
52 : local_state_(local_state), | 84 : local_state_(local_state), |
53 pref_name_(pref_name), | 85 pref_name_(pref_name), |
| 86 old_pref_name_(old_pref_name), |
54 min_log_count_(min_log_count), | 87 min_log_count_(min_log_count), |
55 min_log_bytes_(min_log_bytes), | 88 min_log_bytes_(min_log_bytes), |
56 max_log_size_(max_log_size), | 89 max_log_size_(max_log_size), |
57 last_provisional_store_index_(-1) { | 90 last_provisional_store_index_(-1) { |
58 DCHECK(local_state_); | 91 DCHECK(local_state_); |
59 // One of the limit arguments must be non-zero. | 92 // One of the limit arguments must be non-zero. |
60 DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0); | 93 DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0); |
61 } | 94 } |
62 | 95 |
63 PersistedLogs::~PersistedLogs() {} | 96 PersistedLogs::~PersistedLogs() {} |
64 | 97 |
65 void PersistedLogs::SerializeLogs() { | 98 void PersistedLogs::SerializeLogs() { |
66 // Remove any logs that are over the serialization size limit. | 99 // Remove any logs that are over the serialization size limit. |
67 if (max_log_size_) { | 100 if (max_log_size_) { |
68 for (std::vector<LogHashPair>::iterator it = list_.begin(); | 101 for (std::vector<LogHashPair>::iterator it = list_.begin(); |
69 it != list_.end();) { | 102 it != list_.end();) { |
70 size_t log_size = it->log.length(); | 103 size_t log_size = it->compressed_log_data.length(); |
71 if (log_size > max_log_size_) { | 104 if (log_size > max_log_size_) { |
72 UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", | 105 UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", |
73 static_cast<int>(log_size)); | 106 static_cast<int>(log_size)); |
74 it = list_.erase(it); | 107 it = list_.erase(it); |
75 } else { | 108 } else { |
76 ++it; | 109 ++it; |
77 } | 110 } |
78 } | 111 } |
79 } | 112 } |
| 113 |
80 ListPrefUpdate update(local_state_, pref_name_); | 114 ListPrefUpdate update(local_state_, pref_name_); |
81 WriteLogsToPrefList(update.Get()); | 115 WriteLogsToPrefList(update.Get()); |
| 116 |
| 117 // Clear the old pref now that we've written to the new one. |
| 118 // TODO(asvitkine): Remove the old pref in M39. |
| 119 local_state_->ClearPref(old_pref_name_); |
82 } | 120 } |
83 | 121 |
84 PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() { | 122 PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() { |
85 const base::ListValue* unsent_logs = local_state_->GetList(pref_name_); | 123 // First, try reading from old pref. If it's empty, read from the new one. |
| 124 // TODO(asvitkine): Remove the old pref in M39. |
| 125 const base::ListValue* unsent_logs = local_state_->GetList(old_pref_name_); |
| 126 if (!unsent_logs->empty()) |
| 127 return ReadLogsFromOldPrefList(*unsent_logs); |
| 128 |
| 129 unsent_logs = local_state_->GetList(pref_name_); |
86 return ReadLogsFromPrefList(*unsent_logs); | 130 return ReadLogsFromPrefList(*unsent_logs); |
87 } | 131 } |
88 | 132 |
89 void PersistedLogs::StoreLog(std::string* input) { | 133 void PersistedLogs::StoreLog(const std::string& log_data) { |
90 list_.push_back(LogHashPair()); | 134 list_.push_back(LogHashPair()); |
91 list_.back().SwapLog(input); | 135 list_.back().Init(log_data); |
92 } | 136 } |
93 | 137 |
94 void PersistedLogs::StageLog() { | 138 void PersistedLogs::StageLog() { |
95 // CHECK, rather than DCHECK, because swap()ing with an empty list causes | 139 // CHECK, rather than DCHECK, because swap()ing with an empty list causes |
96 // hard-to-identify crashes much later. | 140 // hard-to-identify crashes much later. |
97 CHECK(!list_.empty()); | 141 CHECK(!list_.empty()); |
98 DCHECK(!has_staged_log()); | 142 DCHECK(!has_staged_log()); |
99 staged_log_.Swap(&list_.back()); | 143 staged_log_.Swap(&list_.back()); |
100 list_.pop_back(); | 144 list_.pop_back(); |
101 | 145 |
102 // If the staged log was the last provisional store, clear that. | 146 // If the staged log was the last provisional store, clear that. |
103 if (static_cast<size_t>(last_provisional_store_index_) == list_.size()) | 147 if (static_cast<size_t>(last_provisional_store_index_) == list_.size()) |
104 last_provisional_store_index_ = -1; | 148 last_provisional_store_index_ = -1; |
105 DCHECK(has_staged_log()); | 149 DCHECK(has_staged_log()); |
106 } | 150 } |
107 | 151 |
108 void PersistedLogs::DiscardStagedLog() { | 152 void PersistedLogs::DiscardStagedLog() { |
109 DCHECK(has_staged_log()); | 153 DCHECK(has_staged_log()); |
110 staged_log_.log.clear(); | 154 staged_log_.Clear(); |
111 } | 155 } |
112 | 156 |
113 void PersistedLogs::StoreStagedLogAsUnsent(StoreType store_type) { | 157 void PersistedLogs::StoreStagedLogAsUnsent(StoreType store_type) { |
114 list_.push_back(LogHashPair()); | 158 list_.push_back(LogHashPair()); |
115 list_.back().Swap(&staged_log_); | 159 list_.back().Swap(&staged_log_); |
116 if (store_type == PROVISIONAL_STORE) | 160 if (store_type == PROVISIONAL_STORE) |
117 last_provisional_store_index_ = list_.size() - 1; | 161 last_provisional_store_index_ = list_.size() - 1; |
118 } | 162 } |
119 | 163 |
120 void PersistedLogs::DiscardLastProvisionalStore() { | 164 void PersistedLogs::DiscardLastProvisionalStore() { |
121 if (last_provisional_store_index_ == -1) | 165 if (last_provisional_store_index_ == -1) |
122 return; | 166 return; |
123 DCHECK_LT(static_cast<size_t>(last_provisional_store_index_), list_.size()); | 167 DCHECK_LT(static_cast<size_t>(last_provisional_store_index_), list_.size()); |
124 list_.erase(list_.begin() + last_provisional_store_index_); | 168 list_.erase(list_.begin() + last_provisional_store_index_); |
125 last_provisional_store_index_ = -1; | 169 last_provisional_store_index_ = -1; |
126 } | 170 } |
127 | 171 |
128 void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) { | 172 void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) { |
129 list_value->Clear(); | 173 list_value->Clear(); |
130 | |
131 // Leave the list completely empty if there are no storable values. | 174 // Leave the list completely empty if there are no storable values. |
132 if (list_.empty()) | 175 if (list_.empty()) |
133 return; | 176 return; |
134 | 177 |
135 size_t start = 0; | 178 size_t start = 0; |
136 // If there are too many logs, keep the most recent logs up to the length | 179 // If there are too many logs, keep the most recent logs up to the length |
137 // limit, and at least to the minimum number of bytes. | 180 // limit, and at least to the minimum number of bytes. |
138 if (list_.size() > min_log_count_) { | 181 if (list_.size() > min_log_count_) { |
139 start = list_.size(); | 182 start = list_.size(); |
140 size_t bytes_used = 0; | 183 size_t bytes_used = 0; |
141 std::vector<LogHashPair>::const_reverse_iterator end = list_.rend(); | 184 std::vector<LogHashPair>::const_reverse_iterator end = list_.rend(); |
142 for (std::vector<LogHashPair>::const_reverse_iterator it = list_.rbegin(); | 185 for (std::vector<LogHashPair>::const_reverse_iterator it = list_.rbegin(); |
143 it != end; ++it) { | 186 it != end; ++it) { |
144 size_t log_size = it->log.length(); | 187 const size_t log_size = it->compressed_log_data.length(); |
145 if (bytes_used >= min_log_bytes_ && | 188 if (bytes_used >= min_log_bytes_ && |
146 (list_.size() - start) >= min_log_count_) { | 189 (list_.size() - start) >= min_log_count_) { |
147 break; | 190 break; |
148 } | 191 } |
149 bytes_used += log_size; | 192 bytes_used += log_size; |
150 --start; | 193 --start; |
151 } | 194 } |
152 } | 195 } |
153 DCHECK_LT(start, list_.size()); | 196 DCHECK_LT(start, list_.size()); |
154 if (start >= list_.size()) | |
155 return; | |
156 | 197 |
157 // Store size at the beginning of the list_value. | 198 for (size_t i = start; i < list_.size(); ++i) { |
158 list_value->Append(base::Value::CreateIntegerValue(list_.size() - start)); | 199 AppendBase64String(list_[i].compressed_log_data, list_value); |
159 | 200 AppendBase64String(list_[i].hash, list_value); |
160 base::MD5Context ctx; | |
161 base::MD5Init(&ctx); | |
162 std::string encoded_log; | |
163 for (std::vector<LogHashPair>::const_iterator it = list_.begin() + start; | |
164 it != list_.end(); ++it) { | |
165 // We encode the compressed log as Value::CreateStringValue() expects to | |
166 // take a valid UTF8 string. | |
167 base::Base64Encode(it->log, &encoded_log); | |
168 base::MD5Update(&ctx, encoded_log); | |
169 list_value->Append(base::Value::CreateStringValue(encoded_log)); | |
170 } | 201 } |
171 | |
172 // Append hash to the end of the list_value. | |
173 base::MD5Digest digest; | |
174 base::MD5Final(&digest, &ctx); | |
175 list_value->Append(base::Value::CreateStringValue( | |
176 base::MD5DigestToBase16(digest))); | |
177 // Minimum of 3 elements (size, data, hash). | |
178 DCHECK_GE(list_value->GetSize(), 3U); | |
179 } | 202 } |
180 | 203 |
181 PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList( | 204 PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList( |
182 const base::ListValue& list_value) { | 205 const base::ListValue& list_value) { |
| 206 if (list_value.empty()) |
| 207 return MakeRecallStatusHistogram(LIST_EMPTY); |
| 208 |
| 209 // For each log, there's two entries in the list (the data and the hash). |
| 210 DCHECK_EQ(0U, list_value.GetSize() % 2); |
| 211 const size_t log_count = list_value.GetSize() / 2; |
| 212 |
| 213 // Resize |list_| ahead of time, so that values can be decoded directly into |
| 214 // the elements of the list. |
| 215 DCHECK(list_.empty()); |
| 216 list_.resize(log_count); |
| 217 |
| 218 for (size_t i = 0; i < log_count; ++i) { |
| 219 if (!ReadBase64String(list_value, i * 2, &list_[i].compressed_log_data) || |
| 220 !ReadBase64String(list_value, i * 2 + 1, &list_[i].hash)) { |
| 221 list_.clear(); |
| 222 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); |
| 223 } |
| 224 } |
| 225 |
| 226 return MakeRecallStatusHistogram(RECALL_SUCCESS); |
| 227 } |
| 228 |
| 229 PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromOldPrefList( |
| 230 const base::ListValue& list_value) { |
| 231 // We append (2) more elements to persisted lists: the size of the list and a |
| 232 // checksum of the elements. |
| 233 const size_t kChecksumEntryCount = 2; |
| 234 |
183 if (list_value.GetSize() == 0) | 235 if (list_value.GetSize() == 0) |
184 return MakeRecallStatusHistogram(LIST_EMPTY); | 236 return MakeRecallStatusHistogram(LIST_EMPTY); |
185 if (list_value.GetSize() <= kChecksumEntryCount) | 237 if (list_value.GetSize() <= kChecksumEntryCount) |
186 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); | 238 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); |
187 | 239 |
188 // The size is stored at the beginning of the list_value. | 240 // The size is stored at the beginning of the list_value. |
189 int size; | 241 int size; |
190 bool valid = (*list_value.begin())->GetAsInteger(&size); | 242 bool valid = (*list_value.begin())->GetAsInteger(&size); |
191 if (!valid) | 243 if (!valid) |
192 return MakeRecallStatusHistogram(LIST_SIZE_MISSING); | 244 return MakeRecallStatusHistogram(LIST_SIZE_MISSING); |
(...skipping 23 matching lines...) Expand all Loading... |
216 | 268 |
217 base::MD5Update(&ctx, encoded_log); | 269 base::MD5Update(&ctx, encoded_log); |
218 | 270 |
219 std::string log_text; | 271 std::string log_text; |
220 if (!base::Base64Decode(encoded_log, &log_text)) { | 272 if (!base::Base64Decode(encoded_log, &log_text)) { |
221 list_.clear(); | 273 list_.clear(); |
222 return MakeRecallStatusHistogram(DECODE_FAIL); | 274 return MakeRecallStatusHistogram(DECODE_FAIL); |
223 } | 275 } |
224 | 276 |
225 DCHECK_LT(local_index, list_.size()); | 277 DCHECK_LT(local_index, list_.size()); |
226 list_[local_index].SwapLog(&log_text); | 278 list_[local_index].Init(log_text); |
227 } | 279 } |
228 | 280 |
229 // Verify checksum. | 281 // Verify checksum. |
230 base::MD5Digest digest; | 282 base::MD5Digest digest; |
231 base::MD5Final(&digest, &ctx); | 283 base::MD5Final(&digest, &ctx); |
232 std::string recovered_md5; | 284 std::string recovered_md5; |
233 // We store the hash at the end of the list_value. | 285 // We store the hash at the end of the list_value. |
234 valid = (*(list_value.end() - 1))->GetAsString(&recovered_md5); | 286 valid = (*(list_value.end() - 1))->GetAsString(&recovered_md5); |
235 if (!valid) { | 287 if (!valid) { |
236 list_.clear(); | 288 list_.clear(); |
237 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); | 289 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); |
238 } | 290 } |
239 if (recovered_md5 != base::MD5DigestToBase16(digest)) { | 291 if (recovered_md5 != base::MD5DigestToBase16(digest)) { |
240 list_.clear(); | 292 list_.clear(); |
241 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); | 293 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); |
242 } | 294 } |
243 return MakeRecallStatusHistogram(RECALL_SUCCESS); | 295 return MakeRecallStatusHistogram(RECALL_SUCCESS); |
244 } | 296 } |
245 | 297 |
246 } // namespace metrics | 298 } // namespace metrics |
OLD | NEW |