// Copyright 2014 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 "components/metrics/metrics_log_manager.h"

#include <algorithm>

#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/timer/elapsed_timer.h"
#include "components/metrics/metrics_log.h"
#include "components/metrics/metrics_pref_names.h"

namespace metrics {

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;

}  // namespace

MetricsLogManager::MetricsLogManager(PrefService* local_state,
                                     size_t max_ongoing_log_size)
    : unsent_logs_loaded_(false),
      initial_log_queue_(local_state,
                         prefs::kMetricsInitialLogs,
                         prefs::kMetricsInitialLogsOld,
                         kInitialLogsPersistLimit,
                         kStorageByteLimitPerLogType,
                         0),
      ongoing_log_queue_(local_state,
                         prefs::kMetricsOngoingLogs,
                         prefs::kMetricsOngoingLogsOld,
                         kOngoingLogsPersistLimit,
                         kStorageByteLimitPerLogType,
                         max_ongoing_log_size) {}

MetricsLogManager::~MetricsLogManager() {}

void MetricsLogManager::BeginLoggingWithLog(scoped_ptr<MetricsLog> log) {
  DCHECK(!current_log_);
  current_log_ = log.Pass();
}

void MetricsLogManager::FinishCurrentLog() {
  DCHECK(current_log_.get());
  current_log_->CloseLog();
  std::string log_data;
  current_log_->GetEncodedLog(&log_data);
  if (!log_data.empty())
    StoreLog(log_data, current_log_->log_type());
  current_log_.reset();
}

void MetricsLogManager::StageNextLogForUpload() {
  DCHECK(!has_staged_log());
  if (!initial_log_queue_.empty())
    initial_log_queue_.StageLog();
  else
    ongoing_log_queue_.StageLog();
}

void MetricsLogManager::DiscardStagedLog() {
  DCHECK(has_staged_log());
  if (initial_log_queue_.has_staged_log())
    initial_log_queue_.DiscardStagedLog();
  else
    ongoing_log_queue_.DiscardStagedLog();
  DCHECK(!has_staged_log());
}

void MetricsLogManager::DiscardCurrentLog() {
  current_log_->CloseLog();
  current_log_.reset();
}

void MetricsLogManager::PauseCurrentLog() {
  DCHECK(!paused_log_.get());
  paused_log_.reset(current_log_.release());
}

void MetricsLogManager::ResumePausedLog() {
  DCHECK(!current_log_.get());
  current_log_.reset(paused_log_.release());
}

void MetricsLogManager::StoreLog(const std::string& log_data,
                                 MetricsLog::LogType log_type) {
  switch (log_type) {
    case MetricsLog::INITIAL_STABILITY_LOG:
      initial_log_queue_.StoreLog(log_data);
      break;
    case MetricsLog::ONGOING_LOG:
      ongoing_log_queue_.StoreLog(log_data);
      break;
  }
}

void MetricsLogManager::StoreStagedLogAsUnsent(
    PersistedLogs::StoreType store_type) {
  DCHECK(has_staged_log());
  if (initial_log_queue_.has_staged_log())
    initial_log_queue_.StoreStagedLogAsUnsent(store_type);
  else
    ongoing_log_queue_.StoreStagedLogAsUnsent(store_type);
}

void MetricsLogManager::DiscardLastProvisionalStore() {
  // We have at most one provisional store, (since at most one log is being
  // uploaded at a time), so at least one of these will be a no-op.
  initial_log_queue_.DiscardLastProvisionalStore();
  ongoing_log_queue_.DiscardLastProvisionalStore();
}

void MetricsLogManager::PersistUnsentLogs() {
  DCHECK(unsent_logs_loaded_);
  if (!unsent_logs_loaded_)
    return;

  base::ElapsedTimer timer;
  initial_log_queue_.SerializeLogs();
  ongoing_log_queue_.SerializeLogs();
  UMA_HISTOGRAM_TIMES("UMA.StoreLogsTime", timer.Elapsed());
}

void MetricsLogManager::LoadPersistedUnsentLogs() {
  base::ElapsedTimer timer;
  initial_log_queue_.DeserializeLogs();
  ongoing_log_queue_.DeserializeLogs();
  UMA_HISTOGRAM_TIMES("UMA.LoadLogsTime", timer.Elapsed());

  unsent_logs_loaded_ = true;
}

}  // namespace metrics