| 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 |