Home | History | Annotate | Download | only in metrics
      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 
     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 
     53 MetricsLogSerializer::MetricsLogSerializer() {}
     54 
     55 MetricsLogSerializer::~MetricsLogSerializer() {}
     56 
     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 
     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
    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
    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