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
3 // found in the LICENSE file.
5 #include "components/metrics/persisted_logs.h"
9 #include "base/base64.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 "base/sha1.h"
15 #include "base/timer/elapsed_timer.h"
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;
25 PersistedLogs::LogReadStatus
MakeRecallStatusHistogram(
26 PersistedLogs::LogReadStatus status
) {
27 UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
28 status
, PersistedLogs::END_RECALL_STATUS
);
34 void PersistedLogs::LogHashPair::SwapLog(std::string
* input
) {
37 hash
= base::SHA1HashString(log
);
42 void PersistedLogs::LogHashPair::Swap(PersistedLogs::LogHashPair
* input
) {
44 hash
.swap(input
->hash
);
47 PersistedLogs::PersistedLogs(PrefService
* local_state
,
48 const char* pref_name
,
52 : local_state_(local_state
),
53 pref_name_(pref_name
),
54 min_log_count_(min_log_count
),
55 min_log_bytes_(min_log_bytes
),
56 max_log_size_(max_log_size
),
57 last_provisional_store_index_(-1) {
59 // One of the limit arguments must be non-zero.
60 DCHECK(min_log_count_
> 0 || min_log_bytes_
> 0);
63 PersistedLogs::~PersistedLogs() {}
65 void PersistedLogs::SerializeLogs() {
66 // Remove any logs that are over the serialization size limit.
68 for (std::vector
<LogHashPair
>::iterator it
= list_
.begin();
70 size_t log_size
= it
->log
.length();
71 if (log_size
> max_log_size_
) {
72 UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
73 static_cast<int>(log_size
));
80 ListPrefUpdate
update(local_state_
, pref_name_
);
81 WriteLogsToPrefList(update
.Get());
84 PersistedLogs::LogReadStatus
PersistedLogs::DeserializeLogs() {
85 const base::ListValue
* unsent_logs
= local_state_
->GetList(pref_name_
);
86 return ReadLogsFromPrefList(*unsent_logs
);
89 void PersistedLogs::StoreLog(std::string
* input
) {
90 list_
.push_back(LogHashPair());
91 list_
.back().SwapLog(input
);
94 void PersistedLogs::StageLog() {
95 // CHECK, rather than DCHECK, because swap()ing with an empty list causes
96 // hard-to-identify crashes much later.
97 CHECK(!list_
.empty());
98 DCHECK(!has_staged_log());
99 staged_log_
.Swap(&list_
.back());
102 // If the staged log was the last provisional store, clear that.
103 if (static_cast<size_t>(last_provisional_store_index_
) == list_
.size())
104 last_provisional_store_index_
= -1;
105 DCHECK(has_staged_log());
108 void PersistedLogs::DiscardStagedLog() {
109 DCHECK(has_staged_log());
110 staged_log_
.log
.clear();
113 void PersistedLogs::StoreStagedLogAsUnsent(StoreType store_type
) {
114 list_
.push_back(LogHashPair());
115 list_
.back().Swap(&staged_log_
);
116 if (store_type
== PROVISIONAL_STORE
)
117 last_provisional_store_index_
= list_
.size() - 1;
120 void PersistedLogs::DiscardLastProvisionalStore() {
121 if (last_provisional_store_index_
== -1)
123 DCHECK_LT(static_cast<size_t>(last_provisional_store_index_
), list_
.size());
124 list_
.erase(list_
.begin() + last_provisional_store_index_
);
125 last_provisional_store_index_
= -1;
128 void PersistedLogs::WriteLogsToPrefList(base::ListValue
* list_value
) {
131 // Leave the list completely empty if there are no storable values.
136 // 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.
138 if (list_
.size() > min_log_count_
) {
139 start
= list_
.size();
140 size_t bytes_used
= 0;
141 std::vector
<LogHashPair
>::const_reverse_iterator end
= list_
.rend();
142 for (std::vector
<LogHashPair
>::const_reverse_iterator it
= list_
.rbegin();
144 size_t log_size
= it
->log
.length();
145 if (bytes_used
>= min_log_bytes_
&&
146 (list_
.size() - start
) >= min_log_count_
) {
149 bytes_used
+= log_size
;
153 DCHECK_LT(start
, list_
.size());
154 if (start
>= list_
.size())
157 // Store size at the beginning of the list_value.
158 list_value
->Append(base::Value::CreateIntegerValue(list_
.size() - start
));
160 base::MD5Context 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
));
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);
181 PersistedLogs::LogReadStatus
PersistedLogs::ReadLogsFromPrefList(
182 const base::ListValue
& list_value
) {
183 if (list_value
.GetSize() == 0)
184 return MakeRecallStatusHistogram(LIST_EMPTY
);
185 if (list_value
.GetSize() <= kChecksumEntryCount
)
186 return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL
);
188 // The size is stored at the beginning of the list_value.
190 bool valid
= (*list_value
.begin())->GetAsInteger(&size
);
192 return MakeRecallStatusHistogram(LIST_SIZE_MISSING
);
193 // Account for checksum and size included in the list_value.
194 if (static_cast<size_t>(size
) != list_value
.GetSize() - kChecksumEntryCount
)
195 return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION
);
197 // Allocate strings for all of the logs we are going to read in.
198 // Do this ahead of time so that we can decode the string values directly into
199 // the elements of |list_|, and thereby avoid making copies of the
200 // serialized logs, which can be fairly large.
201 DCHECK(list_
.empty());
204 base::MD5Context ctx
;
206 std::string encoded_log
;
207 size_t local_index
= 0;
208 for (base::ListValue::const_iterator it
= list_value
.begin() + 1;
209 it
!= list_value
.end() - 1; // Last element is the checksum.
210 ++it
, ++local_index
) {
211 bool valid
= (*it
)->GetAsString(&encoded_log
);
214 return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION
);
217 base::MD5Update(&ctx
, encoded_log
);
219 std::string log_text
;
220 if (!base::Base64Decode(encoded_log
, &log_text
)) {
222 return MakeRecallStatusHistogram(DECODE_FAIL
);
225 DCHECK_LT(local_index
, list_
.size());
226 list_
[local_index
].SwapLog(&log_text
);
230 base::MD5Digest digest
;
231 base::MD5Final(&digest
, &ctx
);
232 std::string recovered_md5
;
233 // We store the hash at the end of the list_value.
234 valid
= (*(list_value
.end() - 1))->GetAsString(&recovered_md5
);
237 return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION
);
239 if (recovered_md5
!= base::MD5DigestToBase16(digest
)) {
241 return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION
);
243 return MakeRecallStatusHistogram(RECALL_SUCCESS
);
246 } // namespace metrics