1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/metrics/metrics_log_serializer.h" 6 7 #include <string> 8 9 #include "base/base64.h" 10 #include "base/md5.h" 11 #include "base/metrics/histogram.h" 12 #include "base/prefs/pref_service.h" 13 #include "base/prefs/scoped_user_pref_update.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/common/pref_names.h" 16 17 namespace { 18 19 // The number of "initial" logs to save, and hope to send during a future Chrome 20 // session. Initial logs contain crash stats, and are pretty small. 21 const size_t kInitialLogsPersistLimit = 20; 22 23 // The number of ongoing logs to save persistently, and hope to 24 // send during a this or future sessions. Note that each log may be pretty 25 // large, as presumably the related "initial" log wasn't sent (probably nothing 26 // was, as the user was probably off-line). As a result, the log probably kept 27 // accumulating while the "initial" log was stalled, and couldn't be sent. As a 28 // result, we don't want to save too many of these mega-logs. 29 // A "standard shutdown" will create a small log, including just the data that 30 // was not yet been transmitted, and that is normal (to have exactly one 31 // ongoing_log_ at startup). 32 const size_t kOngoingLogsPersistLimit = 8; 33 34 // The number of bytes each of initial and ongoing logs that must be stored. 35 // This ensures that a reasonable amount of history will be stored even if there 36 // is a long series of very small logs. 37 const size_t kStorageByteLimitPerLogType = 300000; 38 39 // We append (2) more elements to persisted lists: the size of the list and a 40 // checksum of the elements. 41 const size_t kChecksumEntryCount = 2; 42 43 MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram( 44 MetricsLogSerializer::LogReadStatus status) { 45 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", 46 status, MetricsLogSerializer::END_RECALL_STATUS); 47 return status; 48 } 49 50 } // namespace 51 52 53 MetricsLogSerializer::MetricsLogSerializer() {} 54 55 MetricsLogSerializer::~MetricsLogSerializer() {} 56 57 void MetricsLogSerializer::SerializeLogs( 58 const std::vector<MetricsLogManager::SerializedLog>& logs, 59 MetricsLogManager::LogType log_type) { 60 PrefService* local_state = g_browser_process->local_state(); 61 DCHECK(local_state); 62 const char* pref = NULL; 63 size_t store_length_limit = 0; 64 switch (log_type) { 65 case MetricsLogBase::INITIAL_LOG: 66 pref = prefs::kMetricsInitialLogs; 67 store_length_limit = kInitialLogsPersistLimit; 68 break; 69 case MetricsLogBase::ONGOING_LOG: 70 pref = prefs::kMetricsOngoingLogs; 71 store_length_limit = kOngoingLogsPersistLimit; 72 break; 73 case MetricsLogBase::NO_LOG: 74 NOTREACHED(); 75 return; 76 }; 77 78 ListPrefUpdate update(local_state, pref); 79 WriteLogsToPrefList(logs, store_length_limit, kStorageByteLimitPerLogType, 80 update.Get()); 81 } 82 83 void MetricsLogSerializer::DeserializeLogs( 84 MetricsLogManager::LogType log_type, 85 std::vector<MetricsLogManager::SerializedLog>* logs) { 86 DCHECK(logs); 87 PrefService* local_state = g_browser_process->local_state(); 88 DCHECK(local_state); 89 90 const char* pref; 91 if (log_type == MetricsLogBase::INITIAL_LOG) 92 pref = prefs::kMetricsInitialLogs; 93 else 94 pref = prefs::kMetricsOngoingLogs; 95 96 const ListValue* unsent_logs = local_state->GetList(pref); 97 ReadLogsFromPrefList(*unsent_logs, logs); 98 } 99 100 // static 101 void MetricsLogSerializer::WriteLogsToPrefList( 102 const std::vector<MetricsLogManager::SerializedLog>& local_list, 103 size_t list_length_limit, 104 size_t byte_limit, 105 base::ListValue* list) { 106 // One of the limit arguments must be non-zero. 107 DCHECK(list_length_limit > 0 || byte_limit > 0); 108 109 list->Clear(); 110 if (local_list.size() == 0) 111 return; 112 113 size_t start = 0; 114 // If there are too many logs, keep the most recent logs up to the length 115 // limit, and at least to the minimum number of bytes. 116 if (local_list.size() > list_length_limit) { 117 start = local_list.size(); 118 size_t bytes_used = 0; 119 for (std::vector<MetricsLogManager::SerializedLog>::const_reverse_iterator 120 it = local_list.rbegin(); it != local_list.rend(); ++it) { 121 size_t log_size = it->log_text().length(); 122 if (bytes_used >= byte_limit && 123 (local_list.size() - start) >= list_length_limit) 124 break; 125 bytes_used += log_size; 126 --start; 127 } 128 } 129 DCHECK_LT(start, local_list.size()); 130 if (start >= local_list.size()) 131 return; 132 133 // Store size at the beginning of the list. 134 list->Append(Value::CreateIntegerValue(local_list.size() - start)); 135 136 base::MD5Context ctx; 137 base::MD5Init(&ctx); 138 std::string encoded_log; 139 for (std::vector<MetricsLogManager::SerializedLog>::const_iterator it = 140 local_list.begin() + start; 141 it != local_list.end(); ++it) { 142 // We encode the compressed log as Value::CreateStringValue() expects to 143 // take a valid UTF8 string. 144 base::Base64Encode(it->log_text(), &encoded_log); 145 base::MD5Update(&ctx, encoded_log); 146 list->Append(Value::CreateStringValue(encoded_log)); 147 } 148 149 // Append hash to the end of the list. 150 base::MD5Digest digest; 151 base::MD5Final(&digest, &ctx); 152 list->Append(Value::CreateStringValue(base::MD5DigestToBase16(digest))); 153 DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash). 154 } 155 156 // static 157 MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList( 158 const ListValue& list, 159 std::vector<MetricsLogManager::SerializedLog>* local_list) { 160 if (list.GetSize() == 0) 161 return MakeRecallStatusHistogram(LIST_EMPTY); 162 if (list.GetSize() < 3) 163 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); 164 165 // The size is stored at the beginning of the list. 166 int size; 167 bool valid = (*list.begin())->GetAsInteger(&size); 168 if (!valid) 169 return MakeRecallStatusHistogram(LIST_SIZE_MISSING); 170 // Account for checksum and size included in the list. 171 if (static_cast<unsigned int>(size) != 172 list.GetSize() - kChecksumEntryCount) { 173 return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION); 174 } 175 176 // Allocate strings for all of the logs we are going to read in. 177 // Do this ahead of time so that we can decode the string values directly into 178 // the elements of |local_list|, and thereby avoid making copies of the 179 // serialized logs, which can be fairly large. 180 DCHECK(local_list->empty()); 181 local_list->resize(size); 182 183 base::MD5Context ctx; 184 base::MD5Init(&ctx); 185 std::string encoded_log; 186 size_t local_index = 0; 187 for (ListValue::const_iterator it = list.begin() + 1; 188 it != list.end() - 1; // Last element is the checksum. 189 ++it, ++local_index) { 190 bool valid = (*it)->GetAsString(&encoded_log); 191 if (!valid) { 192 local_list->clear(); 193 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); 194 } 195 196 base::MD5Update(&ctx, encoded_log); 197 198 std::string log_text; 199 if (!base::Base64Decode(encoded_log, &log_text)) { 200 local_list->clear(); 201 return MakeRecallStatusHistogram(DECODE_FAIL); 202 } 203 204 DCHECK_LT(local_index, local_list->size()); 205 (*local_list)[local_index].SwapLogText(&log_text); 206 } 207 208 // Verify checksum. 209 base::MD5Digest digest; 210 base::MD5Final(&digest, &ctx); 211 std::string recovered_md5; 212 // We store the hash at the end of the list. 213 valid = (*(list.end() - 1))->GetAsString(&recovered_md5); 214 if (!valid) { 215 local_list->clear(); 216 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); 217 } 218 if (recovered_md5 != base::MD5DigestToBase16(digest)) { 219 local_list->clear(); 220 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); 221 } 222 return MakeRecallStatusHistogram(RECALL_SUCCESS); 223 } 224