Home | History | Annotate | Download | only in metrics
      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.
      4 
      5 #include "components/metrics/persisted_logs.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 "base/sha1.h"
     15 #include "base/timer/elapsed_timer.h"
     16 #include "components/metrics/compression_utils.h"
     17 
     18 namespace metrics {
     19 
     20 namespace {
     21 
     22 PersistedLogs::LogReadStatus MakeRecallStatusHistogram(
     23     PersistedLogs::LogReadStatus status) {
     24   UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
     25                             status, PersistedLogs::END_RECALL_STATUS);
     26   return status;
     27 }
     28 
     29 // Reads the value at |index| from |list_value| as a string and Base64-decodes
     30 // it into |result|. Returns true on success.
     31 bool ReadBase64String(const base::ListValue& list_value,
     32                       size_t index,
     33                       std::string* result) {
     34   std::string base64_result;
     35   if (!list_value.GetString(index, &base64_result))
     36     return false;
     37   return base::Base64Decode(base64_result, result);
     38 }
     39 
     40 // Base64-encodes |str| and appends the result to |list_value|.
     41 void AppendBase64String(const std::string& str, base::ListValue* list_value) {
     42   std::string base64_str;
     43   base::Base64Encode(str, &base64_str);
     44   list_value->AppendString(base64_str);
     45 }
     46 
     47 }  // namespace
     48 
     49 void PersistedLogs::LogHashPair::Init(const std::string& log_data) {
     50   DCHECK(!log_data.empty());
     51 
     52   if (!GzipCompress(log_data, &compressed_log_data)) {
     53     NOTREACHED();
     54     return;
     55   }
     56 
     57   UMA_HISTOGRAM_PERCENTAGE(
     58       "UMA.ProtoCompressionRatio",
     59       static_cast<int>(100 * compressed_log_data.size() / log_data.size()));
     60   UMA_HISTOGRAM_CUSTOM_COUNTS(
     61       "UMA.ProtoGzippedKBSaved",
     62       static_cast<int>((log_data.size() - compressed_log_data.size()) / 1024),
     63       1, 2000, 50);
     64 
     65   hash = base::SHA1HashString(log_data);
     66 }
     67 
     68 PersistedLogs::PersistedLogs(PrefService* local_state,
     69                              const char* pref_name,
     70                              size_t min_log_count,
     71                              size_t min_log_bytes,
     72                              size_t max_log_size)
     73     : local_state_(local_state),
     74       pref_name_(pref_name),
     75       min_log_count_(min_log_count),
     76       min_log_bytes_(min_log_bytes),
     77       max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)),
     78       staged_log_index_(-1) {
     79   DCHECK(local_state_);
     80   // One of the limit arguments must be non-zero.
     81   DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0);
     82 }
     83 
     84 PersistedLogs::~PersistedLogs() {}
     85 
     86 void PersistedLogs::SerializeLogs() const {
     87   ListPrefUpdate update(local_state_, pref_name_);
     88   WriteLogsToPrefList(update.Get());
     89 }
     90 
     91 PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() {
     92   return ReadLogsFromPrefList(*local_state_->GetList(pref_name_));
     93 }
     94 
     95 void PersistedLogs::StoreLog(const std::string& log_data) {
     96   list_.push_back(LogHashPair());
     97   list_.back().Init(log_data);
     98 }
     99 
    100 void PersistedLogs::StageLog() {
    101   // CHECK, rather than DCHECK, because swap()ing with an empty list causes
    102   // hard-to-identify crashes much later.
    103   CHECK(!list_.empty());
    104   DCHECK(!has_staged_log());
    105   staged_log_index_ = list_.size() - 1;
    106   DCHECK(has_staged_log());
    107 }
    108 
    109 void PersistedLogs::DiscardStagedLog() {
    110   DCHECK(has_staged_log());
    111   DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
    112   list_.erase(list_.begin() + staged_log_index_);
    113   staged_log_index_ = -1;
    114 }
    115 
    116 void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const {
    117   list_value->Clear();
    118 
    119   // Keep the most recent logs which are smaller than |max_log_size_|.
    120   // We keep at least |min_log_bytes_| and |min_log_count_| of logs before
    121   // discarding older logs.
    122   size_t start = list_.size();
    123   size_t saved_log_count = 0;
    124   size_t bytes_used = 0;
    125   for (; start > 0; --start) {
    126     size_t log_size = list_[start - 1].compressed_log_data.length();
    127     if (bytes_used >= min_log_bytes_ &&
    128         saved_log_count >= min_log_count_) {
    129       break;
    130     }
    131     // Oversized logs won't be persisted, so don't count them.
    132     if (log_size > max_log_size_)
    133       continue;
    134     bytes_used += log_size;
    135     ++saved_log_count;
    136   }
    137 
    138   for (size_t i = start; i < list_.size(); ++i) {
    139     size_t log_size = list_[i].compressed_log_data.length();
    140     if (log_size > max_log_size_) {
    141       UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
    142                            static_cast<int>(log_size));
    143       continue;
    144     }
    145     AppendBase64String(list_[i].compressed_log_data, list_value);
    146     AppendBase64String(list_[i].hash, list_value);
    147   }
    148 }
    149 
    150 PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList(
    151     const base::ListValue& list_value) {
    152   if (list_value.empty())
    153     return MakeRecallStatusHistogram(LIST_EMPTY);
    154 
    155   // For each log, there's two entries in the list (the data and the hash).
    156   DCHECK_EQ(0U, list_value.GetSize() % 2);
    157   const size_t log_count = list_value.GetSize() / 2;
    158 
    159   // Resize |list_| ahead of time, so that values can be decoded directly into
    160   // the elements of the list.
    161   DCHECK(list_.empty());
    162   list_.resize(log_count);
    163 
    164   for (size_t i = 0; i < log_count; ++i) {
    165     if (!ReadBase64String(list_value, i * 2, &list_[i].compressed_log_data) ||
    166         !ReadBase64String(list_value, i * 2 + 1, &list_[i].hash)) {
    167       list_.clear();
    168       return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
    169     }
    170   }
    171 
    172   return MakeRecallStatusHistogram(RECALL_SUCCESS);
    173 }
    174 
    175 }  // namespace metrics
    176