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); |
jar (doing other things)
2012/02/27 20:35:34
Looking at the data, this LIST_EMPTY case is the v
Ilya Sherman
2012/02/28 00:23:10
It looks like the MetricsLogManager code reads in
jar (doing other things)
2012/02/28 01:33:21
Yup... that explains it perfectly. Thanks.
On 20
| |
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 // Make room for all of the logs we are going to read in. | |
195 // Do this ahead of time to avoid making copies of the serialized logs, which | |
196 // can be fairly large. | |
jar (doing other things)
2012/02/27 20:35:34
Although the logs may be large, this only grows th
Ilya Sherman
2012/02/28 00:23:10
This is really making sure that we can safely pass
| |
197 if (is_xml) { | |
198 DCHECK(local_list->empty()); | |
199 local_list->resize(size); | |
200 } else if (local_list->size() != static_cast<size_t>(size)) { | |
201 return MakeRecallStatusHistogram(XML_PROTO_MISMATCH, false); | |
160 } | 202 } |
161 | 203 |
162 base::MD5Context ctx; | 204 base::MD5Context ctx; |
163 base::MD5Init(&ctx); | 205 base::MD5Init(&ctx); |
164 std::string encoded_log; | 206 std::string encoded_log; |
165 std::string decoded_log; | 207 size_t local_index = 0; |
166 for (ListValue::const_iterator it = list.begin() + 1; | 208 for (ListValue::const_iterator it = list.begin() + 1; |
167 it != list.end() - 1; ++it) { // Last element is the checksum. | 209 it != list.end() - 1; // Last element is the checksum. |
168 valid = (*it)->GetAsString(&encoded_log); | 210 ++it, ++local_index) { |
211 bool valid = (*it)->GetAsString(&encoded_log); | |
169 if (!valid) { | 212 if (!valid) { |
170 local_list->clear(); | 213 local_list->clear(); |
171 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); | 214 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION, is_xml); |
172 } | 215 } |
173 | 216 |
174 base::MD5Update(&ctx, encoded_log); | 217 base::MD5Update(&ctx, encoded_log); |
175 | 218 |
219 DCHECK_LT(local_index, local_list->size()); | |
220 std::string& decoded_log = is_xml ? | |
221 (*local_list)[local_index].xml : | |
222 (*local_list)[local_index].proto; | |
176 if (!base::Base64Decode(encoded_log, &decoded_log)) { | 223 if (!base::Base64Decode(encoded_log, &decoded_log)) { |
jar (doing other things)
2012/02/27 20:35:34
I'm curious: Does the JSON encoding of prefs autom
Ilya Sherman
2012/02/28 00:23:10
Looks like the base64 encoding/decoding is necessa
jar (doing other things)
2012/02/28 01:33:21
OK. Thanks.
On 2012/02/28 00:23:10, Ilya Sherman
| |
177 local_list->clear(); | 224 local_list->clear(); |
178 return MakeRecallStatusHistogram(DECODE_FAIL); | 225 return MakeRecallStatusHistogram(DECODE_FAIL, is_xml); |
179 } | 226 } |
180 local_list->push_back(decoded_log); | |
181 } | 227 } |
182 | 228 |
183 // Verify checksum. | 229 // Verify checksum. |
184 base::MD5Digest digest; | 230 base::MD5Digest digest; |
185 base::MD5Final(&digest, &ctx); | 231 base::MD5Final(&digest, &ctx); |
186 std::string recovered_md5; | 232 std::string recovered_md5; |
187 // We store the hash at the end of the list. | 233 // We store the hash at the end of the list. |
188 valid = (*(list.end() - 1))->GetAsString(&recovered_md5); | 234 valid = (*(list.end() - 1))->GetAsString(&recovered_md5); |
189 if (!valid) { | 235 if (!valid) { |
190 local_list->clear(); | 236 local_list->clear(); |
191 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); | 237 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION, is_xml); |
192 } | 238 } |
193 if (recovered_md5 != base::MD5DigestToBase16(digest)) { | 239 if (recovered_md5 != base::MD5DigestToBase16(digest)) { |
194 local_list->clear(); | 240 local_list->clear(); |
195 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); | 241 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION, is_xml); |
196 } | 242 } |
197 return MakeRecallStatusHistogram(RECALL_SUCCESS); | 243 return MakeRecallStatusHistogram(RECALL_SUCCESS, is_xml); |
198 } | 244 } |
OLD | NEW |