// Copyright 2020 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 "base/util/memory_pressure/system_memory_pressure_evaluator_linux.h"

#include "base/bind.h"
#include "base/process/process_metrics.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"

namespace {

constexpr int kKiBperMiB = 1024;

int GetAvailableSystemMemoryMiB(const base::SystemMemoryInfoKB& mem_info) {
  // Use 'available' metric if is is present,
  // if no (kernels < 3.14), let's make a rough evaluation using free physical
  // memory plus buffers and caches (that OS can free in case of low memory
  // state)
  int mem_available =
      mem_info.available ? mem_info.available
                         : (mem_info.free + mem_info.buffers + mem_info.cached);
  // How much physical memory is actively available for use right now, in MBs.
  return mem_available / kKiBperMiB;
}

}  // namespace

namespace util {
namespace os_linux {

const base::TimeDelta SystemMemoryPressureEvaluator::kModeratePressureCooldown =
    base::TimeDelta::FromSeconds(10);

const int SystemMemoryPressureEvaluator::kDefaultModerateThresholdPc = 75;
const int SystemMemoryPressureEvaluator::kDefaultCriticalThresholdPc = 85;

SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
    std::unique_ptr<MemoryPressureVoter> voter)
    : util::SystemMemoryPressureEvaluator(std::move(voter)),
      moderate_pressure_repeat_count_(0) {
  if (InferThresholds())
    StartObserving();
}

SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
    int moderate_threshold_mb,
    int critical_threshold_mb,
    std::unique_ptr<MemoryPressureVoter> voter)
    : util::SystemMemoryPressureEvaluator(std::move(voter)),
      moderate_threshold_mb_(moderate_threshold_mb),
      critical_threshold_mb_(critical_threshold_mb),
      moderate_pressure_repeat_count_(0) {
  DCHECK_GE(moderate_threshold_mb_, critical_threshold_mb_);
  DCHECK_GT(critical_threshold_mb_, 0);
  StartObserving();
}

void SystemMemoryPressureEvaluator::StartObserving() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  timer_.Start(
      FROM_HERE, base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod,
      base::BindRepeating(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
                          base::Unretained(this)));
}

void SystemMemoryPressureEvaluator::StopObserving() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // If StartObserving failed, StopObserving will still get called.
  timer_.Stop();
}

bool SystemMemoryPressureEvaluator::GetSystemMemoryInfo(
    base::SystemMemoryInfoKB* mem_info) {
  return base::GetSystemMemoryInfo(mem_info);
}

void SystemMemoryPressureEvaluator::CheckMemoryPressure() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Get the previous pressure level and update the current one.
  MemoryPressureLevel old_vote = current_vote();
  SetCurrentVote(CalculateCurrentPressureLevel());

  // |notify| will be set to true if MemoryPressureListeners need to be
  // notified of a memory pressure level state change.
  bool notify = false;
  switch (current_vote()) {
    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
      break;

    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
      if (old_vote != current_vote()) {
        // This is a new transition to moderate pressure so notify.
        moderate_pressure_repeat_count_ = 0;
        notify = true;
      } else {
        // Already in moderate pressure, only notify if sustained over the
        // cooldown period.
        const int kModeratePressureCooldownCycles =
            kModeratePressureCooldown /
            base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod;
        if (++moderate_pressure_repeat_count_ ==
            kModeratePressureCooldownCycles) {
          moderate_pressure_repeat_count_ = 0;
          notify = true;
        }
      }
      break;

    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
      // Always notify of critical pressure levels.
      notify = true;
      break;
  }

  SendCurrentVote(notify);
}

bool SystemMemoryPressureEvaluator::InferThresholds() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::SystemMemoryInfoKB mem_info;
  if (!GetSystemMemoryInfo(&mem_info))
    return false;
  critical_threshold_mb_ =
      mem_info.total * (float((100 - kDefaultCriticalThresholdPc)) / (100 * kKiBperMiB));
  moderate_threshold_mb_ =
      mem_info.total * (float((100 - kDefaultModerateThresholdPc)) / (100 * kKiBperMiB));
  return true;
}

base::MemoryPressureListener::MemoryPressureLevel
SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel() {
  base::SystemMemoryInfoKB mem_info;
  if (GetSystemMemoryInfo(&mem_info)) {
    // How much system memory is actively available for use right now, in MBs.
    int available = GetAvailableSystemMemoryMiB(mem_info);

    // Determine if the available memory is under critical memory pressure.
    if (available <= critical_threshold_mb_)
      return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;

    // Determine if the available memory is under moderate memory pressure.
    if (available <= moderate_threshold_mb_)
      return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
  }
  // No memory pressure was detected.
  return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
}

}  // namespace os_linux
}  // namespace util
