OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/metrics/metrics_log_serializer.h" | 5 #include "chrome/browser/metrics/metrics_log_serializer.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/md5.h" | 8 #include "base/md5.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "chrome/browser/browser_process.h" | 10 #include "chrome/browser/browser_process.h" |
11 #include "chrome/browser/prefs/pref_service.h" | 11 #include "chrome/browser/prefs/pref_service.h" |
12 #include "chrome/browser/prefs/scoped_user_pref_update.h" | 12 #include "chrome/browser/prefs/scoped_user_pref_update.h" |
13 #include "chrome/common/pref_names.h" | 13 #include "chrome/common/pref_names.h" |
14 | 14 |
| 15 namespace { |
| 16 |
15 // The number of "initial" logs we're willing to save, and hope to send during | 17 // The number of "initial" logs we're willing to save, and hope to send during |
16 // a future Chrome session. Initial logs contain crash stats, and are pretty | 18 // a future Chrome session. Initial logs contain crash stats, and are pretty |
17 // small. | 19 // small. |
18 static const size_t kMaxInitialLogsPersisted = 20; | 20 const size_t kMaxInitialLogsPersisted = 20; |
19 | 21 |
20 // The number of ongoing logs we're willing to save persistently, and hope to | 22 // The number of ongoing logs we're willing to save persistently, and hope to |
21 // send during a this or future sessions. Note that each log may be pretty | 23 // send during a this or future sessions. Note that each log may be pretty |
22 // large, as presumably the related "initial" log wasn't sent (probably nothing | 24 // large, as presumably the related "initial" log wasn't sent (probably nothing |
23 // was, as the user was probably off-line). As a result, the log probably kept | 25 // was, as the user was probably off-line). As a result, the log probably kept |
24 // accumulating while the "initial" log was stalled, and couldn't be sent. As a | 26 // accumulating while the "initial" log was stalled, and couldn't be sent. As a |
25 // result, we don't want to save too many of these mega-logs. | 27 // result, we don't want to save too many of these mega-logs. |
26 // A "standard shutdown" will create a small log, including just the data that | 28 // A "standard shutdown" will create a small log, including just the data that |
27 // was not yet been transmitted, and that is normal (to have exactly one | 29 // was not yet been transmitted, and that is normal (to have exactly one |
28 // ongoing_log_ at startup). | 30 // ongoing_log_ at startup). |
29 static const size_t kMaxOngoingLogsPersisted = 8; | 31 const size_t kMaxOngoingLogsPersisted = 8; |
30 | 32 |
31 // We append (2) more elements to persisted lists: the size of the list and a | 33 // We append (2) more elements to persisted lists: the size of the list and a |
32 // checksum of the elements. | 34 // checksum of the elements. |
33 static const size_t kChecksumEntryCount = 2; | 35 const size_t kChecksumEntryCount = 2; |
34 | 36 |
35 namespace { | 37 // TODO(isherman): Remove this histogram once it's confirmed that there are no |
36 // TODO(ziadh): This is here temporarily for a side experiment. Remove later | 38 // encoding failures for protobuf logs. |
37 // on. | |
38 enum LogStoreStatus { | 39 enum LogStoreStatus { |
39 STORE_SUCCESS, // Successfully presisted log. | 40 STORE_SUCCESS, // Successfully presisted log. |
40 ENCODE_FAIL, // Failed to encode log. | 41 ENCODE_FAIL, // Failed to encode log. |
41 COMPRESS_FAIL, // Failed to compress log. | 42 COMPRESS_FAIL, // Failed to compress log. |
42 END_STORE_STATUS // Number of bins to use to create the histogram. | 43 END_STORE_STATUS // Number of bins to use to create the histogram. |
43 }; | 44 }; |
44 | 45 |
45 MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram( | 46 MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram( |
46 MetricsLogSerializer::LogReadStatus status) { | 47 MetricsLogSerializer::LogReadStatus status, |
47 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall", status, | 48 bool is_xml) { |
48 MetricsLogSerializer::END_RECALL_STATUS); | 49 if (is_xml) { |
| 50 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall", |
| 51 status, MetricsLogSerializer::END_RECALL_STATUS); |
| 52 } else { |
| 53 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", |
| 54 status, MetricsLogSerializer::END_RECALL_STATUS); |
| 55 } |
49 return status; | 56 return status; |
50 } | 57 } |
51 | 58 |
52 // TODO(ziadh): Remove this when done with experiment. | |
53 void MakeStoreStatusHistogram(LogStoreStatus status) { | 59 void MakeStoreStatusHistogram(LogStoreStatus status) { |
54 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore2", status, | 60 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore2", status, |
55 END_STORE_STATUS); | 61 END_STORE_STATUS); |
56 } | 62 } |
| 63 |
57 } // namespace | 64 } // namespace |
58 | 65 |
59 | 66 |
60 MetricsLogSerializer::MetricsLogSerializer() {} | 67 MetricsLogSerializer::MetricsLogSerializer() {} |
61 | 68 |
62 MetricsLogSerializer::~MetricsLogSerializer() {} | 69 MetricsLogSerializer::~MetricsLogSerializer() {} |
63 | 70 |
64 void MetricsLogSerializer::SerializeLogs(const std::vector<std::string>& logs, | 71 void MetricsLogSerializer::SerializeLogs( |
65 MetricsLogManager::LogType log_type) { | 72 const std::vector<MetricsLogManager::SerializedLog>& logs, |
| 73 MetricsLogManager::LogType log_type) { |
66 PrefService* local_state = g_browser_process->local_state(); | 74 PrefService* local_state = g_browser_process->local_state(); |
67 DCHECK(local_state); | 75 DCHECK(local_state); |
68 const char* pref = NULL; | 76 const char* pref_xml = NULL; |
| 77 const char* pref_proto = NULL; |
69 size_t max_store_count = 0; | 78 size_t max_store_count = 0; |
70 switch (log_type) { | 79 switch (log_type) { |
71 case MetricsLogManager::INITIAL_LOG: | 80 case MetricsLogManager::INITIAL_LOG: |
72 pref = prefs::kMetricsInitialLogs; | 81 pref_xml = prefs::kMetricsInitialLogsXml; |
| 82 pref_proto = prefs::kMetricsInitialLogsProto; |
73 max_store_count = kMaxInitialLogsPersisted; | 83 max_store_count = kMaxInitialLogsPersisted; |
74 break; | 84 break; |
75 case MetricsLogManager::ONGOING_LOG: | 85 case MetricsLogManager::ONGOING_LOG: |
76 pref = prefs::kMetricsOngoingLogs; | 86 pref_xml = prefs::kMetricsOngoingLogsXml; |
| 87 pref_proto = prefs::kMetricsOngoingLogsProto; |
77 max_store_count = kMaxOngoingLogsPersisted; | 88 max_store_count = kMaxOngoingLogsPersisted; |
78 break; | 89 break; |
79 default: | 90 default: |
80 NOTREACHED(); | 91 NOTREACHED(); |
81 return; | 92 return; |
82 }; | 93 }; |
83 ListPrefUpdate update(local_state, pref); | 94 |
84 ListValue* pref_list = update.Get(); | 95 // Write the XML version. |
85 WriteLogsToPrefList(logs, max_store_count, pref_list); | 96 ListPrefUpdate update_xml(local_state, pref_xml); |
| 97 WriteLogsToPrefList(logs, true, max_store_count, update_xml.Get()); |
| 98 |
| 99 // Write the protobuf version. |
| 100 ListPrefUpdate update_proto(local_state, pref_proto); |
| 101 WriteLogsToPrefList(logs, false, max_store_count, update_proto.Get()); |
86 } | 102 } |
87 | 103 |
88 void MetricsLogSerializer::DeserializeLogs(MetricsLogManager::LogType log_type, | 104 void MetricsLogSerializer::DeserializeLogs( |
89 std::vector<std::string>* logs) { | 105 MetricsLogManager::LogType log_type, |
| 106 std::vector<MetricsLogManager::SerializedLog>* logs) { |
90 DCHECK(logs); | 107 DCHECK(logs); |
91 PrefService* local_state = g_browser_process->local_state(); | 108 PrefService* local_state = g_browser_process->local_state(); |
92 DCHECK(local_state); | 109 DCHECK(local_state); |
93 | 110 |
94 const char* pref = (log_type == MetricsLogManager::INITIAL_LOG) ? | 111 const char* pref_xml; |
95 prefs::kMetricsInitialLogs : prefs::kMetricsOngoingLogs; | 112 const char* pref_proto; |
96 const ListValue* unsent_logs = local_state->GetList(pref); | 113 if (log_type == MetricsLogManager::INITIAL_LOG) { |
97 ReadLogsFromPrefList(*unsent_logs, logs); | 114 pref_xml = prefs::kMetricsInitialLogsXml; |
| 115 pref_proto = prefs::kMetricsInitialLogsProto; |
| 116 } else { |
| 117 pref_xml = prefs::kMetricsOngoingLogsXml; |
| 118 pref_proto = prefs::kMetricsOngoingLogsProto; |
| 119 } |
| 120 |
| 121 const ListValue* unsent_logs_xml = local_state->GetList(pref_xml); |
| 122 const ListValue* unsent_logs_proto = local_state->GetList(pref_proto); |
| 123 if (ReadLogsFromPrefList(*unsent_logs_xml, true, logs) == RECALL_SUCCESS) { |
| 124 // In order to try to keep the data sent to both servers roughly in sync, |
| 125 // only read the protobuf data if we read the XML data successfully. |
| 126 ReadLogsFromPrefList(*unsent_logs_proto, false, logs); |
| 127 } |
98 } | 128 } |
99 | 129 |
100 // static | 130 // static |
101 void MetricsLogSerializer::WriteLogsToPrefList( | 131 void MetricsLogSerializer::WriteLogsToPrefList( |
102 const std::vector<std::string>& local_list, | 132 const std::vector<MetricsLogManager::SerializedLog>& local_list, |
103 const size_t kMaxLocalListSize, | 133 bool is_xml, |
104 ListValue* list) { | 134 size_t max_list_size, |
| 135 base::ListValue* list) { |
105 list->Clear(); | 136 list->Clear(); |
106 size_t start = 0; | 137 size_t start = 0; |
107 if (local_list.size() > kMaxLocalListSize) | 138 if (local_list.size() > max_list_size) |
108 start = local_list.size() - kMaxLocalListSize; | 139 start = local_list.size() - max_list_size; |
109 DCHECK(start <= local_list.size()); | 140 DCHECK_LE(start, local_list.size()); |
110 if (local_list.size() <= start) | 141 if (local_list.size() <= start) |
111 return; | 142 return; |
112 | 143 |
113 // Store size at the beginning of the list. | 144 // Store size at the beginning of the list. |
114 list->Append(Value::CreateIntegerValue(local_list.size() - start)); | 145 list->Append(Value::CreateIntegerValue(local_list.size() - start)); |
115 | 146 |
116 base::MD5Context ctx; | 147 base::MD5Context ctx; |
117 base::MD5Init(&ctx); | 148 base::MD5Init(&ctx); |
118 std::string encoded_log; | 149 std::string encoded_log; |
119 for (std::vector<std::string>::const_iterator it = local_list.begin() + start; | 150 for (std::vector<MetricsLogManager::SerializedLog>::const_iterator it = |
| 151 local_list.begin() + start; |
120 it != local_list.end(); ++it) { | 152 it != local_list.end(); ++it) { |
| 153 const std::string& value = is_xml ? it->xml : it->proto; |
121 // We encode the compressed log as Value::CreateStringValue() expects to | 154 // We encode the compressed log as Value::CreateStringValue() expects to |
122 // take a valid UTF8 string. | 155 // take a valid UTF8 string. |
123 if (!base::Base64Encode(*it, &encoded_log)) { | 156 if (!base::Base64Encode(value, &encoded_log)) { |
124 MakeStoreStatusHistogram(ENCODE_FAIL); | 157 MakeStoreStatusHistogram(ENCODE_FAIL); |
125 list->Clear(); | 158 list->Clear(); |
126 return; | 159 return; |
127 } | 160 } |
128 base::MD5Update(&ctx, encoded_log); | 161 base::MD5Update(&ctx, encoded_log); |
129 list->Append(Value::CreateStringValue(encoded_log)); | 162 list->Append(Value::CreateStringValue(encoded_log)); |
130 } | 163 } |
131 | 164 |
132 // Append hash to the end of the list. | 165 // Append hash to the end of the list. |
133 base::MD5Digest digest; | 166 base::MD5Digest digest; |
134 base::MD5Final(&digest, &ctx); | 167 base::MD5Final(&digest, &ctx); |
135 list->Append(Value::CreateStringValue(base::MD5DigestToBase16(digest))); | 168 list->Append(Value::CreateStringValue(base::MD5DigestToBase16(digest))); |
136 DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash). | 169 DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash). |
137 MakeStoreStatusHistogram(STORE_SUCCESS); | 170 MakeStoreStatusHistogram(STORE_SUCCESS); |
138 } | 171 } |
139 | 172 |
140 // static | 173 // static |
141 MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList( | 174 MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList( |
142 const ListValue& list, | 175 const ListValue& list, |
143 std::vector<std::string>* local_list) { | 176 bool is_xml, |
144 DCHECK(local_list->empty()); | 177 std::vector<MetricsLogManager::SerializedLog>* local_list) { |
145 if (list.GetSize() == 0) | 178 if (list.GetSize() == 0) |
146 return MakeRecallStatusHistogram(LIST_EMPTY); | 179 return MakeRecallStatusHistogram(LIST_EMPTY, is_xml); |
147 if (list.GetSize() < 3) | 180 if (list.GetSize() < 3) |
148 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); | 181 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL, is_xml); |
149 | 182 |
150 // The size is stored at the beginning of the list. | 183 // The size is stored at the beginning of the list. |
151 int size; | 184 int size; |
152 bool valid = (*list.begin())->GetAsInteger(&size); | 185 bool valid = (*list.begin())->GetAsInteger(&size); |
153 if (!valid) | 186 if (!valid) |
154 return MakeRecallStatusHistogram(LIST_SIZE_MISSING); | 187 return MakeRecallStatusHistogram(LIST_SIZE_MISSING, is_xml); |
155 | |
156 // Account for checksum and size included in the list. | 188 // Account for checksum and size included in the list. |
157 if (static_cast<unsigned int>(size) != | 189 if (static_cast<unsigned int>(size) != |
158 list.GetSize() - kChecksumEntryCount) { | 190 list.GetSize() - kChecksumEntryCount) { |
159 return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION); | 191 return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION, is_xml); |
| 192 } |
| 193 |
| 194 // Allocate strings for all of the logs we are going to read in. |
| 195 // Do this ahead of time so that we can decode the string values directly into |
| 196 // the elements of |local_list|, and thereby avoid making copies of the |
| 197 // serialized logs, which can be fairly large. |
| 198 if (is_xml) { |
| 199 DCHECK(local_list->empty()); |
| 200 local_list->resize(size); |
| 201 } else if (local_list->size() != static_cast<size_t>(size)) { |
| 202 return MakeRecallStatusHistogram(XML_PROTO_MISMATCH, false); |
160 } | 203 } |
161 | 204 |
162 base::MD5Context ctx; | 205 base::MD5Context ctx; |
163 base::MD5Init(&ctx); | 206 base::MD5Init(&ctx); |
164 std::string encoded_log; | 207 std::string encoded_log; |
165 std::string decoded_log; | 208 size_t local_index = 0; |
166 for (ListValue::const_iterator it = list.begin() + 1; | 209 for (ListValue::const_iterator it = list.begin() + 1; |
167 it != list.end() - 1; ++it) { // Last element is the checksum. | 210 it != list.end() - 1; // Last element is the checksum. |
168 valid = (*it)->GetAsString(&encoded_log); | 211 ++it, ++local_index) { |
| 212 bool valid = (*it)->GetAsString(&encoded_log); |
169 if (!valid) { | 213 if (!valid) { |
170 local_list->clear(); | 214 local_list->clear(); |
171 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); | 215 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION, is_xml); |
172 } | 216 } |
173 | 217 |
174 base::MD5Update(&ctx, encoded_log); | 218 base::MD5Update(&ctx, encoded_log); |
175 | 219 |
| 220 DCHECK_LT(local_index, local_list->size()); |
| 221 std::string& decoded_log = is_xml ? |
| 222 (*local_list)[local_index].xml : |
| 223 (*local_list)[local_index].proto; |
176 if (!base::Base64Decode(encoded_log, &decoded_log)) { | 224 if (!base::Base64Decode(encoded_log, &decoded_log)) { |
177 local_list->clear(); | 225 local_list->clear(); |
178 return MakeRecallStatusHistogram(DECODE_FAIL); | 226 return MakeRecallStatusHistogram(DECODE_FAIL, is_xml); |
179 } | 227 } |
180 local_list->push_back(decoded_log); | |
181 } | 228 } |
182 | 229 |
183 // Verify checksum. | 230 // Verify checksum. |
184 base::MD5Digest digest; | 231 base::MD5Digest digest; |
185 base::MD5Final(&digest, &ctx); | 232 base::MD5Final(&digest, &ctx); |
186 std::string recovered_md5; | 233 std::string recovered_md5; |
187 // We store the hash at the end of the list. | 234 // We store the hash at the end of the list. |
188 valid = (*(list.end() - 1))->GetAsString(&recovered_md5); | 235 valid = (*(list.end() - 1))->GetAsString(&recovered_md5); |
189 if (!valid) { | 236 if (!valid) { |
190 local_list->clear(); | 237 local_list->clear(); |
191 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); | 238 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION, is_xml); |
192 } | 239 } |
193 if (recovered_md5 != base::MD5DigestToBase16(digest)) { | 240 if (recovered_md5 != base::MD5DigestToBase16(digest)) { |
194 local_list->clear(); | 241 local_list->clear(); |
195 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); | 242 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION, is_xml); |
196 } | 243 } |
197 return MakeRecallStatusHistogram(RECALL_SUCCESS); | 244 return MakeRecallStatusHistogram(RECALL_SUCCESS, is_xml); |
198 } | 245 } |
OLD | NEW |