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 "base/base64.h"
      8 #include "base/md5.h"
      9 #include "base/metrics/histogram.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "chrome/browser/browser_process.h"
     12 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     13 #include "chrome/common/pref_names.h"
     14 
     15 namespace {
     16 
     17 // The number of "initial" logs to save, and hope to send during a future Chrome
     18 // session.  Initial logs contain crash stats, and are pretty small.
     19 const size_t kInitialLogsPersistLimit = 20;
     20 
     21 // The number of ongoing logs to save persistently, and hope to
     22 // send during a this or future sessions.  Note that each log may be pretty
     23 // large, as presumably the related "initial" log wasn't sent (probably nothing
     24 // was, as the user was probably off-line).  As a result, the log probably kept
     25 // accumulating while the "initial" log was stalled, and couldn't be sent.  As a
     26 // result, we don't want to save too many of these mega-logs.
     27 // A "standard shutdown" will create a small log, including just the data that
     28 // was not yet been transmitted, and that is normal (to have exactly one
     29 // ongoing_log_ at startup).
     30 const size_t kOngoingLogsPersistLimit = 8;
     31 
     32 // The number of bytes each of initial and ongoing logs that must be stored.
     33 // This ensures that a reasonable amount of history will be stored even if there
     34 // is a long series of very small logs.
     35 const size_t kStorageByteLimitPerLogType = 300000;
     36 
     37 // We append (2) more elements to persisted lists: the size of the list and a
     38 // checksum of the elements.
     39 const size_t kChecksumEntryCount = 2;
     40 
     41 MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram(
     42     MetricsLogSerializer::LogReadStatus status) {
     43   UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
     44                             status, MetricsLogSerializer::END_RECALL_STATUS);
     45   return status;
     46 }
     47 
     48 }  // namespace
     49 
     50 
     51 MetricsLogSerializer::MetricsLogSerializer() {}
     52 
     53 MetricsLogSerializer::~MetricsLogSerializer() {}
     54 
     55 void MetricsLogSerializer::SerializeLogs(const std::vector<std::string>& logs,
     56                                          MetricsLogManager::LogType log_type) {
     57   PrefService* local_state = g_browser_process->local_state();
     58   DCHECK(local_state);
     59   const char* pref = NULL;
     60   size_t store_length_limit = 0;
     61   switch (log_type) {
     62     case MetricsLogManager::INITIAL_LOG:
     63       pref = prefs::kMetricsInitialLogs;
     64       store_length_limit = kInitialLogsPersistLimit;
     65       break;
     66     case MetricsLogManager::ONGOING_LOG:
     67       pref = prefs::kMetricsOngoingLogs;
     68       store_length_limit = kOngoingLogsPersistLimit;
     69       break;
     70     case MetricsLogManager::NO_LOG:
     71       NOTREACHED();
     72       return;
     73   };
     74 
     75   ListPrefUpdate update(local_state, pref);
     76   WriteLogsToPrefList(logs, store_length_limit, kStorageByteLimitPerLogType,
     77                       update.Get());
     78 }
     79 
     80 void MetricsLogSerializer::DeserializeLogs(MetricsLogManager::LogType log_type,
     81                                            std::vector<std::string>* logs) {
     82   DCHECK(logs);
     83   PrefService* local_state = g_browser_process->local_state();
     84   DCHECK(local_state);
     85 
     86   const char* pref;
     87   if (log_type == MetricsLogManager::INITIAL_LOG)
     88     pref = prefs::kMetricsInitialLogs;
     89   else
     90     pref = prefs::kMetricsOngoingLogs;
     91 
     92   const ListValue* unsent_logs = local_state->GetList(pref);
     93   ReadLogsFromPrefList(*unsent_logs, logs);
     94 }
     95 
     96 // static
     97 void MetricsLogSerializer::WriteLogsToPrefList(
     98     const std::vector<std::string>& local_list,
     99     size_t list_length_limit,
    100     size_t byte_limit,
    101     base::ListValue* list) {
    102   // One of the limit arguments must be non-zero.
    103   DCHECK(list_length_limit > 0 || byte_limit > 0);
    104 
    105   list->Clear();
    106   if (local_list.size() == 0)
    107     return;
    108 
    109   size_t start = 0;
    110   // If there are too many logs, keep the most recent logs up to the length
    111   // limit, and at least to the minimum number of bytes.
    112   if (local_list.size() > list_length_limit) {
    113     start = local_list.size();
    114     size_t bytes_used = 0;
    115     for (std::vector<std::string>::const_reverse_iterator
    116          it = local_list.rbegin(); it != local_list.rend(); ++it) {
    117       size_t log_size = it->length();
    118       if (bytes_used >= byte_limit &&
    119           (local_list.size() - start) >= list_length_limit)
    120         break;
    121       bytes_used += log_size;
    122       --start;
    123     }
    124   }
    125   DCHECK_LT(start, local_list.size());
    126   if (start >= local_list.size())
    127     return;
    128 
    129   // Store size at the beginning of the list.
    130   list->Append(Value::CreateIntegerValue(local_list.size() - start));
    131 
    132   base::MD5Context ctx;
    133   base::MD5Init(&ctx);
    134   std::string encoded_log;
    135   for (std::vector<std::string>::const_iterator it = local_list.begin() + start;
    136        it != local_list.end(); ++it) {
    137     // We encode the compressed log as Value::CreateStringValue() expects to
    138     // take a valid UTF8 string.
    139     if (!base::Base64Encode(*it, &encoded_log)) {
    140       list->Clear();
    141       return;
    142     }
    143     base::MD5Update(&ctx, encoded_log);
    144     list->Append(Value::CreateStringValue(encoded_log));
    145   }
    146 
    147   // Append hash to the end of the list.
    148   base::MD5Digest digest;
    149   base::MD5Final(&digest, &ctx);
    150   list->Append(Value::CreateStringValue(base::MD5DigestToBase16(digest)));
    151   DCHECK(list->GetSize() >= 3);  // Minimum of 3 elements (size, data, hash).
    152 }
    153 
    154 // static
    155 MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList(
    156     const ListValue& list,
    157     std::vector<std::string>* local_list) {
    158   if (list.GetSize() == 0)
    159     return MakeRecallStatusHistogram(LIST_EMPTY);
    160   if (list.GetSize() < 3)
    161     return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL);
    162 
    163   // The size is stored at the beginning of the list.
    164   int size;
    165   bool valid = (*list.begin())->GetAsInteger(&size);
    166   if (!valid)
    167     return MakeRecallStatusHistogram(LIST_SIZE_MISSING);
    168   // Account for checksum and size included in the list.
    169   if (static_cast<unsigned int>(size) !=
    170       list.GetSize() - kChecksumEntryCount) {
    171     return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION);
    172   }
    173 
    174   // Allocate strings for all of the logs we are going to read in.
    175   // Do this ahead of time so that we can decode the string values directly into
    176   // the elements of |local_list|, and thereby avoid making copies of the
    177   // serialized logs, which can be fairly large.
    178   DCHECK(local_list->empty());
    179   local_list->resize(size);
    180 
    181   base::MD5Context ctx;
    182   base::MD5Init(&ctx);
    183   std::string encoded_log;
    184   size_t local_index = 0;
    185   for (ListValue::const_iterator it = list.begin() + 1;
    186        it != list.end() - 1;  // Last element is the checksum.
    187        ++it, ++local_index) {
    188     bool valid = (*it)->GetAsString(&encoded_log);
    189     if (!valid) {
    190       local_list->clear();
    191       return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
    192     }
    193 
    194     base::MD5Update(&ctx, encoded_log);
    195 
    196     DCHECK_LT(local_index, local_list->size());
    197     std::string& decoded_log = (*local_list)[local_index];
    198     if (!base::Base64Decode(encoded_log, &decoded_log)) {
    199       local_list->clear();
    200       return MakeRecallStatusHistogram(DECODE_FAIL);
    201     }
    202   }
    203 
    204   // Verify checksum.
    205   base::MD5Digest digest;
    206   base::MD5Final(&digest, &ctx);
    207   std::string recovered_md5;
    208   // We store the hash at the end of the list.
    209   valid = (*(list.end() - 1))->GetAsString(&recovered_md5);
    210   if (!valid) {
    211     local_list->clear();
    212     return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION);
    213   }
    214   if (recovered_md5 != base::MD5DigestToBase16(digest)) {
    215     local_list->clear();
    216     return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION);
    217   }
    218   return MakeRecallStatusHistogram(RECALL_SUCCESS);
    219 }
    220