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
MakeRecallStatusHistogram(MetricsLogSerializer::LogReadStatus status)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
MetricsLogSerializer()53 MetricsLogSerializer::MetricsLogSerializer() {}
54
~MetricsLogSerializer()55 MetricsLogSerializer::~MetricsLogSerializer() {}
56
SerializeLogs(const std::vector<MetricsLogManager::SerializedLog> & logs,MetricsLogManager::LogType log_type)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
DeserializeLogs(MetricsLogManager::LogType log_type,std::vector<MetricsLogManager::SerializedLog> * logs)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
WriteLogsToPrefList(const std::vector<MetricsLogManager::SerializedLog> & local_list,size_t list_length_limit,size_t byte_limit,base::ListValue * list)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
ReadLogsFromPrefList(const ListValue & list,std::vector<MetricsLogManager::SerializedLog> * local_list)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