1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/dbus_memory_pressure_evaluator_linux.h"
9 #include "base/containers/contains.h"
10 #include "base/functional/bind.h"
11 #include "base/logging.h"
12 #include "base/memory/memory_pressure_listener.h"
13 #include "base/memory/scoped_refptr.h"
14 #include "chrome/common/chrome_features.h"
15 #include "components/dbus/thread_linux/dbus_thread_linux.h"
16 #include "dbus/message.h"
17 #include "dbus/object_path.h"
18 #include "dbus/object_proxy.h"
22 scoped_refptr<dbus::Bus> CreateBusOfType(dbus::Bus::BusType type) {
23 dbus::Bus::Options options;
24 options.bus_type = type;
25 options.connection_type = dbus::Bus::PRIVATE;
26 options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
27 return base::MakeRefCounted<dbus::Bus>(options);
32 const char DbusMemoryPressureEvaluatorLinux::kMethodNameHasOwner[] =
34 const char DbusMemoryPressureEvaluatorLinux::kMethodListActivatableNames[] =
35 "ListActivatableNames";
37 const char DbusMemoryPressureEvaluatorLinux::kLmmService[] =
38 "org.freedesktop.LowMemoryMonitor";
39 const char DbusMemoryPressureEvaluatorLinux::kLmmObject[] =
40 "/org/freedesktop/LowMemoryMonitor";
41 const char DbusMemoryPressureEvaluatorLinux::kLmmInterface[] =
42 "org.freedesktop.LowMemoryMonitor";
44 const char DbusMemoryPressureEvaluatorLinux::kXdgPortalService[] =
45 "org.freedesktop.portal.Desktop";
46 const char DbusMemoryPressureEvaluatorLinux::kXdgPortalObject[] =
47 "/org/freedesktop/portal/desktop";
49 DbusMemoryPressureEvaluatorLinux::kXdgPortalMemoryMonitorInterface[] =
50 "org.freedesktop.portal.MemoryMonitor";
52 const char DbusMemoryPressureEvaluatorLinux::kLowMemoryWarningSignal[] =
55 // LMM emits signals every 15 seconds on pressure, so if we've been quiet for 20
56 // seconds, the pressure is likely cleared up.
57 const base::TimeDelta DbusMemoryPressureEvaluatorLinux::kResetVotePeriod =
60 DbusMemoryPressureEvaluatorLinux::DbusMemoryPressureEvaluatorLinux(
61 std::unique_ptr<memory_pressure::MemoryPressureVoter> voter)
62 : DbusMemoryPressureEvaluatorLinux(std::move(voter), nullptr, nullptr) {
63 // Only start the service checks in the public constructor, so the tests can
64 // have time to set up mocks first when using the private constructor.
65 CheckIfLmmIsAvailable();
68 DbusMemoryPressureEvaluatorLinux::DbusMemoryPressureEvaluatorLinux(
69 std::unique_ptr<memory_pressure::MemoryPressureVoter> voter,
70 scoped_refptr<dbus::Bus> system_bus,
71 scoped_refptr<dbus::Bus> session_bus)
72 : memory_pressure::SystemMemoryPressureEvaluator(std::move(voter)),
73 system_bus_(system_bus),
74 session_bus_(session_bus) {
75 moderate_level_ = features::kLinuxLowMemoryMonitorModerateLevel.Get();
76 critical_level_ = features::kLinuxLowMemoryMonitorCriticalLevel.Get();
78 CHECK(critical_level_ > moderate_level_);
81 DbusMemoryPressureEvaluatorLinux::~DbusMemoryPressureEvaluatorLinux() {
83 system_bus_->ShutdownOnDBusThreadAndBlock();
88 session_bus_->ShutdownOnDBusThreadAndBlock();
93 void DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailable() {
94 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
97 system_bus_ = CreateBusOfType(dbus::Bus::SYSTEM);
99 CheckIfServiceIsAvailable(
100 system_bus_, kLmmService,
102 &DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailableResponse,
103 weak_ptr_factory_.GetWeakPtr()));
106 void DbusMemoryPressureEvaluatorLinux::CheckIfLmmIsAvailableResponse(
108 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
111 VLOG(1) << "LMM is available, using " << kLmmInterface;
114 system_bus_->GetObjectProxy(kLmmService, dbus::ObjectPath(kLmmObject));
115 object_proxy_->ConnectToSignal(
116 kLmmInterface, kLowMemoryWarningSignal,
118 &DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning,
119 weak_ptr_factory_.GetWeakPtr()),
120 base::BindOnce(&DbusMemoryPressureEvaluatorLinux::OnSignalConnected,
121 weak_ptr_factory_.GetWeakPtr()));
123 VLOG(1) << "LMM is not available, checking for portal";
125 ResetBus(system_bus_);
126 CheckIfPortalIsAvailable();
130 void DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailable() {
131 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
134 session_bus_ = CreateBusOfType(dbus::Bus::SESSION);
136 CheckIfServiceIsAvailable(
137 session_bus_, kXdgPortalService,
139 &DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailableResponse,
140 weak_ptr_factory_.GetWeakPtr()));
143 void DbusMemoryPressureEvaluatorLinux::CheckIfPortalIsAvailableResponse(
145 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
148 VLOG(1) << "Portal is available, using "
149 << kXdgPortalMemoryMonitorInterface;
151 object_proxy_ = session_bus_->GetObjectProxy(
152 kXdgPortalService, dbus::ObjectPath(kXdgPortalObject));
153 object_proxy_->ConnectToSignal(
154 kXdgPortalMemoryMonitorInterface, kLowMemoryWarningSignal,
156 &DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning,
157 weak_ptr_factory_.GetWeakPtr()),
158 base::BindOnce(&DbusMemoryPressureEvaluatorLinux::OnSignalConnected,
159 weak_ptr_factory_.GetWeakPtr()));
161 VLOG(1) << "No memory monitor found";
163 ResetBus(session_bus_);
167 void DbusMemoryPressureEvaluatorLinux::CheckIfServiceIsAvailable(
168 scoped_refptr<dbus::Bus> bus,
169 const std::string& service,
170 base::OnceCallback<void(bool)> callback) {
171 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
173 dbus::ObjectProxy* dbus_proxy =
174 bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
176 dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodNameHasOwner);
177 dbus::MessageWriter writer(&method_call);
178 writer.AppendString(service);
180 dbus_proxy->CallMethod(
181 &method_call, DBUS_TIMEOUT_USE_DEFAULT,
182 base::BindOnce(&DbusMemoryPressureEvaluatorLinux::OnNameHasOwnerResponse,
183 weak_ptr_factory_.GetWeakPtr(), std::move(bus), service,
184 std::move(callback)));
187 void DbusMemoryPressureEvaluatorLinux::OnNameHasOwnerResponse(
188 scoped_refptr<dbus::Bus> bus,
189 const std::string& service,
190 base::OnceCallback<void(bool)> callback,
191 dbus::Response* response) {
192 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
194 bool is_running = false;
197 dbus::MessageReader reader(response);
200 if (!reader.PopBool(&owned)) {
201 LOG(ERROR) << "Failed to read " << kMethodNameHasOwner << " response";
206 LOG(ERROR) << "Failed to call " << kMethodNameHasOwner;
210 std::move(callback).Run(true);
212 dbus::ObjectProxy* dbus_proxy = bus->GetObjectProxy(
213 DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
215 dbus::MethodCall method_call(DBUS_INTERFACE_DBUS,
216 kMethodListActivatableNames);
217 dbus_proxy->CallMethod(
218 &method_call, DBUS_TIMEOUT_USE_DEFAULT,
220 &DbusMemoryPressureEvaluatorLinux::OnListActivatableNamesResponse,
221 weak_ptr_factory_.GetWeakPtr(), std::move(service),
222 std::move(callback)));
226 void DbusMemoryPressureEvaluatorLinux::OnListActivatableNamesResponse(
227 const std::string& service,
228 base::OnceCallback<void(bool)> callback,
229 dbus::Response* response) {
230 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
232 bool is_activatable = false;
235 dbus::MessageReader reader(response);
236 std::vector<std::string> names;
237 if (!reader.PopArrayOfStrings(&names)) {
238 LOG(ERROR) << "Failed to read " << kMethodListActivatableNames
240 } else if (base::Contains(names, service)) {
241 is_activatable = true;
244 LOG(ERROR) << "Failed to call " << kMethodListActivatableNames;
247 std::move(callback).Run(is_activatable);
250 void DbusMemoryPressureEvaluatorLinux::ResetBus(scoped_refptr<dbus::Bus>& bus) {
254 bus->GetDBusTaskRunner()->PostTask(
255 FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, bus));
259 void DbusMemoryPressureEvaluatorLinux::OnSignalConnected(
260 const std::string& interface,
261 const std::string& signal,
263 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
266 LOG(WARNING) << "Failed to connect to " << interface << '.' << signal;
268 ResetBus(system_bus_);
269 ResetBus(session_bus_);
273 void DbusMemoryPressureEvaluatorLinux::OnLowMemoryWarning(
274 dbus::Signal* signal) {
275 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
277 dbus::MessageReader reader(signal);
279 if (!reader.PopByte(&lmm_level)) {
280 LOG(WARNING) << "Failed to parse low memory level";
284 // static_cast is needed as lmm_level is a uint8_t, which is often an alias to
285 // char, meaning that sending it to the output stream would just print the
286 // character representation rather than the numeric representation.
287 VLOG(1) << "Monitor sent memory pressure level: "
288 << static_cast<int>(lmm_level);
290 base::MemoryPressureListener::MemoryPressureLevel new_level =
291 LmmToBasePressureLevel(lmm_level);
293 VLOG(1) << "MemoryPressureLevel: " << new_level;
294 UpdateLevel(new_level);
297 base::MemoryPressureListener::MemoryPressureLevel
298 DbusMemoryPressureEvaluatorLinux::LmmToBasePressureLevel(uint8_t lmm_level) {
299 if (lmm_level >= critical_level_)
300 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
301 if (lmm_level >= moderate_level_)
302 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
303 return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
306 void DbusMemoryPressureEvaluatorLinux::UpdateLevel(
307 base::MemoryPressureListener::MemoryPressureLevel new_level) {
308 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
310 reset_vote_timer_.Stop();
312 SetCurrentVote(new_level);
314 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
315 // By convention no notifications are sent when returning to NONE level.
316 SendCurrentVote(false);
318 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
319 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
320 SendCurrentVote(true);
322 reset_vote_timer_.Start(
323 FROM_HERE, kResetVotePeriod,
325 &DbusMemoryPressureEvaluatorLinux::UpdateLevel,
326 weak_ptr_factory_.GetWeakPtr(),
327 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE));