| // Copyright 2021 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/dbus_memory_pressure_evaluator_linux.h" | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "base/containers/contains.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/memory_pressure_listener.h" | 
 | #include "base/memory/scoped_refptr.h" | 
 | #include "chrome/common/chrome_features.h" | 
 | #include "components/dbus/thread_linux/dbus_thread_linux.h" | 
 | #include "components/dbus/utils/check_for_service_and_start.h" | 
 | #include "dbus/message.h" | 
 | #include "dbus/object_path.h" | 
 | #include "dbus/object_proxy.h" | 
 |  | 
 | const char DbusMemoryPressureEvaluatorLinux::kLmmService[] = | 
 |     "org.freedesktop.LowMemoryMonitor"; | 
 | const char DbusMemoryPressureEvaluatorLinux::kLmmObject[] = | 
 |     "/org/freedesktop/LowMemoryMonitor"; | 
 | const char DbusMemoryPressureEvaluatorLinux::kLmmInterface[] = | 
 |     "org.freedesktop.LowMemoryMonitor"; | 
 |  | 
 | const char DbusMemoryPressureEvaluatorLinux::kXdgPortalService[] = | 
 |     "org.freedesktop.portal.Desktop"; | 
 | const char DbusMemoryPressureEvaluatorLinux::kXdgPortalObject[] = | 
 |     "/org/freedesktop/portal/desktop"; | 
 | const char | 
 |     DbusMemoryPressureEvaluatorLinux::kXdgPortalMemoryMonitorInterface[] = | 
 |         "org.freedesktop.portal.MemoryMonitor"; | 
 |  | 
 | const char DbusMemoryPressureEvaluatorLinux::kLowMemoryWarningSignal[] = | 
 |     "LowMemoryWarning"; | 
 |  | 
 | // LMM emits signals every 15 seconds on pressure, so if we've been quiet for 20 | 
 | // seconds, the pressure is likely cleared up. | 
 | const base::TimeDelta DbusMemoryPressureEvaluatorLinux::kResetVotePeriod = | 
 |     base::Seconds(20); | 
 |  | 
 | DbusMemoryPressureEvaluatorLinux::DbusMemoryPressureEvaluatorLinux( | 
 |     std::unique_ptr<memory_pressure::MemoryPressureVoter> voter) | 
 |     : DbusMemoryPressureEvaluatorLinux(std::move(voter), nullptr, nullptr) { | 
 |   // Only start the service checks in the public constructor, so the tests can | 
 |   // have time to set up mocks first when using the private constructor. | 
 |   CheckIfLmmIsAvailable(); | 
 | } | 
 |  | 
 | DbusMemoryPressureEvaluatorLinux::DbusMemoryPressureEvaluatorLinux( | 
 |     std::unique_ptr<memory_pressure::MemoryPressureVoter> voter, | 
 |     scoped_refptr<dbus::Bus> system_bus, | 
 |     scoped_refptr<dbus::Bus> session_bus) | 
 |     : memory_pressure::SystemMemoryPressureEvaluator(std::move(voter)), | 
 |       system_bus_(system_bus), | 
 |       session_bus_(session_bus) { | 
 |   moderate_level_ = features::kLinuxLowMemoryMonitorModerateLevel.Get(); | 
 |   critical_level_ = features::kLinuxLowMemoryMonitorCriticalLevel.Get(); | 
 |  | 
 |   CHECK(critical_level_ > moderate_level_); | 
 | } | 
 |  | 
 | DbusMemoryPressureEvaluatorLinux::~DbusMemoryPressureEvaluatorLinux() { | 
 |   if (system_bus_) { | 
 |     system_bus_->ShutdownOnDBusThreadAndBlock(); | 
 |     system_bus_.reset(); | 
 |   } | 
 |  | 
 |   if (session_bus_) { | 
 |     session_bus_->ShutdownOnDBusThreadAndBlock(); | 
 |     session_bus_.reset(); | 
 |   } | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailable() { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   if (!system_bus_) { | 
 |     system_bus_ = dbus_thread_linux::GetSharedSystemBus(); | 
 |   } | 
 |  | 
 |   dbus_utils::CheckForServiceAndStart( | 
 |       system_bus_, kLmmService, | 
 |       base::BindOnce( | 
 |           &DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailableResponse, | 
 |           weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailableResponse( | 
 |     std::optional<bool> is_available) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   if (is_available.value_or(false)) { | 
 |     VLOG(1) << "LMM is available, using " << kLmmInterface; | 
 |  | 
 |     object_proxy_ = | 
 |         system_bus_->GetObjectProxy(kLmmService, dbus::ObjectPath(kLmmObject)); | 
 |     object_proxy_->ConnectToSignal( | 
 |         kLmmInterface, kLowMemoryWarningSignal, | 
 |         base::BindRepeating( | 
 |             &DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning, | 
 |             weak_ptr_factory_.GetWeakPtr()), | 
 |         base::BindOnce(&DbusMemoryPressureEvaluatorLinux::OnSignalConnected, | 
 |                        weak_ptr_factory_.GetWeakPtr())); | 
 |   } else { | 
 |     VLOG(1) << "LMM is not available, checking for portal"; | 
 |  | 
 |     system_bus_.reset(); | 
 |     CheckIfPortalIsAvailable(); | 
 |   } | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailable() { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   if (!session_bus_) { | 
 |     session_bus_ = dbus_thread_linux::GetSharedSessionBus(); | 
 |   } | 
 |  | 
 |   dbus_utils::CheckForServiceAndStart( | 
 |       session_bus_, kXdgPortalService, | 
 |       base::BindOnce( | 
 |           &DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailableResponse, | 
 |           weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailableResponse( | 
 |     std::optional<bool> is_available) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   if (is_available.value_or(false)) { | 
 |     VLOG(1) << "Portal is available, using " | 
 |             << kXdgPortalMemoryMonitorInterface; | 
 |  | 
 |     object_proxy_ = session_bus_->GetObjectProxy( | 
 |         kXdgPortalService, dbus::ObjectPath(kXdgPortalObject)); | 
 |     object_proxy_->ConnectToSignal( | 
 |         kXdgPortalMemoryMonitorInterface, kLowMemoryWarningSignal, | 
 |         base::BindRepeating( | 
 |             &DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning, | 
 |             weak_ptr_factory_.GetWeakPtr()), | 
 |         base::BindOnce(&DbusMemoryPressureEvaluatorLinux::OnSignalConnected, | 
 |                        weak_ptr_factory_.GetWeakPtr())); | 
 |   } else { | 
 |     VLOG(1) << "No memory monitor found"; | 
 |  | 
 |     session_bus_.reset(); | 
 |   } | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::OnSignalConnected( | 
 |     const std::string& interface, | 
 |     const std::string& signal, | 
 |     bool connected) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   if (!connected) { | 
 |     LOG(WARNING) << "Failed to connect to " << interface << '.' << signal; | 
 |  | 
 |     system_bus_.reset(); | 
 |     session_bus_.reset(); | 
 |   } | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning( | 
 |     dbus::Signal* signal) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   dbus::MessageReader reader(signal); | 
 |   uint8_t lmm_level; | 
 |   if (!reader.PopByte(&lmm_level)) { | 
 |     LOG(WARNING) << "Failed to parse low memory level"; | 
 |     return; | 
 |   } | 
 |  | 
 |   // static_cast is needed as lmm_level is a uint8_t, which is often an alias to | 
 |   // char, meaning that sending it to the output stream would just print the | 
 |   // character representation rather than the numeric representation. | 
 |   VLOG(1) << "Monitor sent memory pressure level: " | 
 |           << static_cast<int>(lmm_level); | 
 |  | 
 |   base::MemoryPressureLevel new_level = LmmToBasePressureLevel(lmm_level); | 
 |  | 
 |   VLOG(1) << "MemoryPressureLevel: " << new_level; | 
 |   UpdateLevel(new_level); | 
 | } | 
 |  | 
 | base::MemoryPressureLevel | 
 | DbusMemoryPressureEvaluatorLinux::LmmToBasePressureLevel(uint8_t lmm_level) { | 
 |   if (lmm_level >= critical_level_) { | 
 |     return base::MEMORY_PRESSURE_LEVEL_CRITICAL; | 
 |   } | 
 |   if (lmm_level >= moderate_level_) { | 
 |     return base::MEMORY_PRESSURE_LEVEL_MODERATE; | 
 |   } | 
 |   return base::MEMORY_PRESSURE_LEVEL_NONE; | 
 | } | 
 |  | 
 | void DbusMemoryPressureEvaluatorLinux::UpdateLevel( | 
 |     base::MemoryPressureLevel new_level) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   reset_vote_timer_.Stop(); | 
 |  | 
 |   SetCurrentVote(new_level); | 
 |   switch (new_level) { | 
 |     case base::MEMORY_PRESSURE_LEVEL_NONE: | 
 |       // By convention no notifications are sent when returning to NONE level. | 
 |       SendCurrentVote(false); | 
 |       break; | 
 |     case base::MEMORY_PRESSURE_LEVEL_MODERATE: | 
 |     case base::MEMORY_PRESSURE_LEVEL_CRITICAL: | 
 |       SendCurrentVote(true); | 
 |  | 
 |       reset_vote_timer_.Start( | 
 |           FROM_HERE, kResetVotePeriod, | 
 |           base::BindOnce(&DbusMemoryPressureEvaluatorLinux::UpdateLevel, | 
 |                          weak_ptr_factory_.GetWeakPtr(), | 
 |                          base::MEMORY_PRESSURE_LEVEL_NONE)); | 
 |       break; | 
 |   } | 
 | } |