blob: 95d5271154575a2bd0dccd39995a50e62b8656bb [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/metrics_log_serializer.h"
#include "base/base64.h"
#include "base/md5.h"
#include "base/metrics/histogram.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/common/pref_names.h"
namespace {
// The number of "initial" logs to save, and hope to send during a future Chrome
// session. Initial logs contain crash stats, and are pretty small.
const size_t kInitialLogsPersistLimit = 20;
// The number of ongoing logs to save persistently, and hope to
// send during a this or future sessions. Note that each log may be pretty
// large, as presumably the related "initial" log wasn't sent (probably nothing
// was, as the user was probably off-line). As a result, the log probably kept
// accumulating while the "initial" log was stalled, and couldn't be sent. As a
// result, we don't want to save too many of these mega-logs.
// A "standard shutdown" will create a small log, including just the data that
// was not yet been transmitted, and that is normal (to have exactly one
// ongoing_log_ at startup).
const size_t kOngoingLogsPersistLimit = 8;
// The number of bytes each of initial and ongoing logs that must be stored.
// This ensures that a reasonable amount of history will be stored even if there
// is a long series of very small logs.
const size_t kStorageByteLimitPerLogType = 300000;
// We append (2) more elements to persisted lists: the size of the list and a
// checksum of the elements.
const size_t kChecksumEntryCount = 2;
// TODO(isherman): Remove this histogram once it's confirmed that there are no
// encoding failures for protobuf logs.
enum LogStoreStatus {
STORE_SUCCESS, // Successfully presisted log.
ENCODE_FAIL, // Failed to encode log.
COMPRESS_FAIL, // Failed to compress log.
END_STORE_STATUS // Number of bins to use to create the histogram.
};
MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram(
MetricsLogSerializer::LogReadStatus status,
bool is_xml) {
if (is_xml) {
UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall",
status, MetricsLogSerializer::END_RECALL_STATUS);
} else {
UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
status, MetricsLogSerializer::END_RECALL_STATUS);
}
return status;
}
void MakeStoreStatusHistogram(LogStoreStatus status) {
UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore2", status,
END_STORE_STATUS);
}
} // namespace
MetricsLogSerializer::MetricsLogSerializer() {}
MetricsLogSerializer::~MetricsLogSerializer() {}
void MetricsLogSerializer::SerializeLogs(
const std::vector<MetricsLogManager::SerializedLog>& logs,
MetricsLogManager::LogType log_type) {
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
const char* pref_xml = NULL;
const char* pref_proto = NULL;
size_t store_length_limit = 0;
switch (log_type) {
case MetricsLogManager::INITIAL_LOG:
pref_xml = prefs::kMetricsInitialLogsXml;
pref_proto = prefs::kMetricsInitialLogsProto;
store_length_limit = kInitialLogsPersistLimit;
break;
case MetricsLogManager::ONGOING_LOG:
pref_xml = prefs::kMetricsOngoingLogsXml;
pref_proto = prefs::kMetricsOngoingLogsProto;
store_length_limit = kOngoingLogsPersistLimit;
break;
default:
NOTREACHED();
return;
};
// Write the XML version.
ListPrefUpdate update_xml(local_state, pref_xml);
WriteLogsToPrefList(logs, true, store_length_limit,
kStorageByteLimitPerLogType, update_xml.Get());
// Write the protobuf version.
ListPrefUpdate update_proto(local_state, pref_proto);
WriteLogsToPrefList(logs, false, store_length_limit,
kStorageByteLimitPerLogType, update_proto.Get());
}
void MetricsLogSerializer::DeserializeLogs(
MetricsLogManager::LogType log_type,
std::vector<MetricsLogManager::SerializedLog>* logs) {
DCHECK(logs);
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
const char* pref_xml;
const char* pref_proto;
if (log_type == MetricsLogManager::INITIAL_LOG) {
pref_xml = prefs::kMetricsInitialLogsXml;
pref_proto = prefs::kMetricsInitialLogsProto;
} else {
pref_xml = prefs::kMetricsOngoingLogsXml;
pref_proto = prefs::kMetricsOngoingLogsProto;
}
const ListValue* unsent_logs_xml = local_state->GetList(pref_xml);
const ListValue* unsent_logs_proto = local_state->GetList(pref_proto);
if (ReadLogsFromPrefList(*unsent_logs_xml, true, logs) == RECALL_SUCCESS) {
// In order to try to keep the data sent to both servers roughly in sync,
// only read the protobuf data if we read the XML data successfully.
ReadLogsFromPrefList(*unsent_logs_proto, false, logs);
}
}
// static
void MetricsLogSerializer::WriteLogsToPrefList(
const std::vector<MetricsLogManager::SerializedLog>& local_list,
bool is_xml,
size_t list_length_limit,
size_t byte_limit,
base::ListValue* list) {
// One of the limit arguments must be non-zero.
DCHECK(list_length_limit > 0 || byte_limit > 0);
list->Clear();
if (local_list.size() == 0)
return;
size_t start = 0;
// If there are too many logs, keep the most recent logs up to the length
// limit, and at least to the minimum number of bytes.
if (local_list.size() > list_length_limit) {
start = local_list.size();
size_t bytes_used = 0;
for (std::vector<MetricsLogManager::SerializedLog>::const_reverse_iterator
it = local_list.rbegin(); it != local_list.rend(); ++it) {
size_t log_size = it->length();
if (bytes_used >= byte_limit &&
(local_list.size() - start) >= list_length_limit)
break;
bytes_used += log_size;
--start;
}
}
DCHECK_LT(start, local_list.size());
if (start >= local_list.size())
return;
// Store size at the beginning of the list.
list->Append(Value::CreateIntegerValue(local_list.size() - start));
base::MD5Context ctx;
base::MD5Init(&ctx);
std::string encoded_log;
for (std::vector<MetricsLogManager::SerializedLog>::const_iterator it =
local_list.begin() + start;
it != local_list.end(); ++it) {
const std::string& value = is_xml ? it->xml : it->proto;
// We encode the compressed log as Value::CreateStringValue() expects to
// take a valid UTF8 string.
if (!base::Base64Encode(value, &encoded_log)) {
MakeStoreStatusHistogram(ENCODE_FAIL);
list->Clear();
return;
}
base::MD5Update(&ctx, encoded_log);
list->Append(Value::CreateStringValue(encoded_log));
}
// Append hash to the end of the list.
base::MD5Digest digest;
base::MD5Final(&digest, &ctx);
list->Append(Value::CreateStringValue(base::MD5DigestToBase16(digest)));
DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash).
MakeStoreStatusHistogram(STORE_SUCCESS);
}
// static
MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList(
const ListValue& list,
bool is_xml,
std::vector<MetricsLogManager::SerializedLog>* local_list) {
if (list.GetSize() == 0)
return MakeRecallStatusHistogram(LIST_EMPTY, is_xml);
if (list.GetSize() < 3)
return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL, is_xml);
// The size is stored at the beginning of the list.
int size;
bool valid = (*list.begin())->GetAsInteger(&size);
if (!valid)
return MakeRecallStatusHistogram(LIST_SIZE_MISSING, is_xml);
// Account for checksum and size included in the list.
if (static_cast<unsigned int>(size) !=
list.GetSize() - kChecksumEntryCount) {
return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION, is_xml);
}
// Allocate strings for all of the logs we are going to read in.
// Do this ahead of time so that we can decode the string values directly into
// the elements of |local_list|, and thereby avoid making copies of the
// serialized logs, which can be fairly large.
if (is_xml) {
DCHECK(local_list->empty());
local_list->resize(size);
} else if (local_list->size() != static_cast<size_t>(size)) {
return MakeRecallStatusHistogram(XML_PROTO_MISMATCH, false);
}
base::MD5Context ctx;
base::MD5Init(&ctx);
std::string encoded_log;
size_t local_index = 0;
for (ListValue::const_iterator it = list.begin() + 1;
it != list.end() - 1; // Last element is the checksum.
++it, ++local_index) {
bool valid = (*it)->GetAsString(&encoded_log);
if (!valid) {
local_list->clear();
return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION, is_xml);
}
base::MD5Update(&ctx, encoded_log);
DCHECK_LT(local_index, local_list->size());
std::string& decoded_log = is_xml ?
(*local_list)[local_index].xml :
(*local_list)[local_index].proto;
if (!base::Base64Decode(encoded_log, &decoded_log)) {
local_list->clear();
return MakeRecallStatusHistogram(DECODE_FAIL, is_xml);
}
}
// Verify checksum.
base::MD5Digest digest;
base::MD5Final(&digest, &ctx);
std::string recovered_md5;
// We store the hash at the end of the list.
valid = (*(list.end() - 1))->GetAsString(&recovered_md5);
if (!valid) {
local_list->clear();
return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION, is_xml);
}
if (recovered_md5 != base::MD5DigestToBase16(digest)) {
local_list->clear();
return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION, is_xml);
}
return MakeRecallStatusHistogram(RECALL_SUCCESS, is_xml);
}