Skip to content

Commit 9eb583d

Browse files
ONEMPERS-120 adding memory watcher
1 parent ea8a61c commit 9eb583d

File tree

4 files changed

+195
-2
lines changed

4 files changed

+195
-2
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ Supported environment variables:
122122
where <string> can be any of syslog(3) prioritynames or its
123123
unique abbreviation e.g. "err", "warning", "info" or "debug".
124124
125+
FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH=<string>
126+
if the app is run inside lxc container - path to where cgroup/memory memory.usage_in_bytes & cgroup.event_control are located
127+
necessary for memory watcher to work
128+
129+
FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES=<string>
130+
if FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH is defined, this specifies container memory usage levels at which the application
131+
will get FlutterEngineNotifyLowMemoryWarning notifications; the format is comma-separated memory (in bytes) values, like:
132+
"1000000,70000000,148478361,167038156,176318054"
133+
At least one memory level needs to be defined for memory watcher to work. After each notification, there is 20sec cooldown period,
134+
when additional FlutterEngineNotifyLowMemoryWarning notifications will not be called.
125135
126136
```
127137

src/main.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ Supported environment variables:
6161
FLUTTER_LAUNCHER_WAYLAND_DEBUG=<string>
6262
where <string> can be any of syslog(3) prioritynames or its
6363
unique abbreviation e.g. "err", "warning", "info" or "debug".
64+
65+
FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH=<string>
66+
if the app is run inside lxc container - path to where cgroup/memory memory.usage_in_bytes & cgroup.event_control are located
67+
necessary for memory watcher to work
68+
69+
FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES=<string>
70+
if FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH is defined, this specifies container memory usage levels at which the application will get
71+
FlutterEngineNotifyLowMemoryWarning notifications; the format is comma-separated memory (in bytes) values, like:
72+
"1000000,70000000,148478361,167038156,176318054"
73+
At least one memory level needs to be defined for memory watcher to work. After each notification, there is 20sec cooldown period,
74+
when additional FlutterEngineNotifyLowMemoryWarning notifications will not be called.
6475
)~" << std::endl;
6576
}
6677

src/wayland_display.cc

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,18 @@
3535
#include "egl_utils.h"
3636
#include "wayland_display.h"
3737

38+
#include <sstream>
39+
3840
#include <unistd.h>
3941
#include <sys/syscall.h>
4042

43+
#include <sys/eventfd.h>
44+
#include <fcntl.h>
45+
4146
#define DBG_TIMING(x)
4247

48+
#define MEMWATCHTAG "memwatcher: "
49+
4350
DBG_TIMING(
4451
#define gettid() syscall(SYS_gettid)
4552
static uint64_t t00;)
@@ -692,6 +699,8 @@ bool WaylandDisplay::SetupEngine(const std::string &bundle_path, const std::vect
692699
}
693700
}
694701

702+
SetupMemoryWatcher();
703+
695704
if (window_metrix_skipped_) {
696705
FlutterWindowMetricsEvent event = {};
697706

@@ -710,7 +719,143 @@ bool WaylandDisplay::SetupEngine(const std::string &bundle_path, const std::vect
710719
return true;
711720
}
712721

722+
void WaylandDisplay::HandleMemoryWatcherEvent() {
723+
// minimum 20 sec between FlutterEngineNotifyLowMemoryWarning calls
724+
constexpr auto min_time_between_warnings_ns_ = uint64_t(2e10);
725+
const uint64_t now_ns = FlutterEngineGetCurrentTime();
726+
constexpr int readsz = 32;
727+
728+
if (now_ns < memory_watcher_.last_warning_sent_ns + min_time_between_warnings_ns_) {
729+
return;
730+
}
731+
732+
lseek(memory_watcher_.memory_file_fd, 0, SEEK_SET);
733+
734+
char memstr[readsz];
735+
int pos = 0;
736+
int rv = 0;
737+
do {
738+
rv = read(memory_watcher_.memory_file_fd, memstr + pos, readsz - 1 - pos);
739+
if (rv != -1)
740+
pos += rv;
741+
} while ((rv == -1 && errno == EINTR) || rv > 0);
742+
memstr[pos] = '\0';
743+
if (rv == -1) {
744+
dbgW(MEMWATCHTAG "problem reading memory_usage, errno:%d\n", errno);
745+
return;
746+
}
747+
748+
dbgT(MEMWATCHTAG "current mem: %s\n", memstr);
749+
const long mem = atol(memstr);
750+
if (mem > 0) {
751+
long level = 0;
752+
for (auto memwatchlevel : memory_watcher_.levels) {
753+
if (memwatchlevel > mem)
754+
break;
755+
else
756+
++level;
757+
}
758+
dbgT(MEMWATCHTAG "current memory level: %ld\n", level);
759+
if (level > memory_watcher_.current_level) {
760+
dbgW(MEMWATCHTAG "sending FlutterEngineNotifyLowMemoryWarning\n");
761+
auto ret = FlutterEngineNotifyLowMemoryWarning(engine_);
762+
if (ret != kSuccess) {
763+
dbgE(MEMWATCHTAG "FlutterEngineNotifyLowMemoryWarning failed with %d\n", int(ret));
764+
return;
765+
}
766+
memory_watcher_.last_warning_sent_ns = now_ns;
767+
}
768+
memory_watcher_.current_level = level;
769+
}
770+
}
771+
772+
void WaylandDisplay::CleanupMemoryWatcher() {
773+
if (memory_watcher_.memory_file_fd != -1) {
774+
close(memory_watcher_.memory_file_fd);
775+
memory_watcher_.memory_file_fd = -1;
776+
}
777+
if (memory_watcher_.event_fd != -1) {
778+
close(memory_watcher_.event_fd);
779+
memory_watcher_.event_fd = -1;
780+
}
781+
}
782+
783+
void WaylandDisplay::SetupMemoryWatcher() {
784+
const std::string cgroup_memory_path = getEnv("FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH", std::string());
785+
if (cgroup_memory_path.empty()) {
786+
dbgI(MEMWATCHTAG "Memory watcher will not run - no FLUTTER_LAUNCHER_WAYLAND_CGROUP_MEMORY_PATH env var defined\n");
787+
} else {
788+
dbgT("SetupMemoryWatcher start, cgroup_memory_path: %s\n", cgroup_memory_path.c_str());
789+
const std::string memory_usage_path = cgroup_memory_path + "/memory.usage_in_bytes";
790+
const std::string cgroup_event_control_path = cgroup_memory_path + "/cgroup.event_control";
791+
const std::string memoryWarningLevels = getEnv("FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES", std::string());
792+
if (memoryWarningLevels.empty()) {
793+
dbgI(MEMWATCHTAG "Memory watcher will not run - no FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES env var defined\n");
794+
} else {
795+
std::stringstream ss{memoryWarningLevels};
796+
std::string level;
797+
while (std::getline(ss, level, ',')) {
798+
if (const long l = atol(level.c_str())) {
799+
memory_watcher_.levels.push_back(l);
800+
} else {
801+
dbgE(MEMWATCHTAG "Memory watcher will not run - could not perform conversion to long for %s; FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES: %s\n", level.c_str(), memoryWarningLevels.c_str());
802+
return;
803+
}
804+
}
805+
if (memory_watcher_.levels.size() < 1) {
806+
dbgW(MEMWATCHTAG "Memory watcher will not run - needs at least 1 memory level; FLUTTER_LAUNCHER_WAYLAND_MEMORY_WARNING_WATERMARK_BYTES: %s\n", memoryWarningLevels.c_str());
807+
} else {
808+
int event_control = -1;
809+
810+
bool all_succeeded = false;
811+
auto cleanup = [&](bool *) {
812+
if (!all_succeeded) {
813+
CleanupMemoryWatcher();
814+
}
815+
if (event_control != -1) {
816+
close(event_control);
817+
event_control = -1;
818+
}
819+
};
820+
const auto cleanup_watcher = std::unique_ptr<bool, decltype(cleanup)>(&all_succeeded, cleanup);
821+
822+
memory_watcher_.memory_file_fd = open(memory_usage_path.c_str(), O_RDONLY);
823+
if (memory_watcher_.memory_file_fd == -1) {
824+
dbgE(MEMWATCHTAG "Cannot open %s, errno: %d\n", memory_usage_path.c_str(), errno);
825+
return;
826+
}
827+
828+
event_control = open(cgroup_event_control_path.c_str(), O_WRONLY);
829+
if (event_control == -1) {
830+
dbgE(MEMWATCHTAG "Cannot open %s, errno: %d\n", cgroup_event_control_path.c_str(), errno);
831+
return;
832+
}
833+
834+
memory_watcher_.event_fd = eventfd(0, 0);
835+
if (memory_watcher_.event_fd == -1) {
836+
dbgE(MEMWATCHTAG "eventfd() failed\n");
837+
return;
838+
}
839+
840+
for (auto level : memory_watcher_.levels) {
841+
char line[LINE_MAX];
842+
snprintf(line, LINE_MAX, "%d %d %ld", memory_watcher_.event_fd, memory_watcher_.memory_file_fd, level);
843+
dbgT(MEMWATCHTAG "event_control: writing line '%s'\n", line);
844+
const int ret = write(event_control, line, strlen(line) + 1);
845+
if (ret == -1) {
846+
dbgE(MEMWATCHTAG "Cannot write to cgroup.event_control, errno: %d\n", errno);
847+
return;
848+
}
849+
}
850+
dbgI(MEMWATCHTAG "starting memory watcher\n");
851+
all_succeeded = true;
852+
}
853+
}
854+
}
855+
}
856+
713857
WaylandDisplay::~WaylandDisplay() {
858+
CleanupMemoryWatcher();
714859

715860
if (engine_) {
716861
auto result = FlutterEngineShutdown(engine_);
@@ -892,10 +1037,11 @@ bool WaylandDisplay::Run() {
8921037
do {
8931038
int rv;
8941039

895-
struct pollfd fds[3] = {
1040+
struct pollfd fds[4] = {
8961041
{.fd = vsync.sv_[vsync.SOCKET_READER], .events = POLLIN, .revents = 0},
8971042
{.fd = fd, .events = POLLIN | POLLERR, .revents = 0},
8981043
{.fd = key.timer_fd_, .events = POLLIN | POLLERR, .revents = 0},
1044+
{.fd = memory_watcher_.event_fd, .events = POLLIN | POLLERR, .revents = 0},
8991045
};
9001046

9011047
do {
@@ -905,7 +1051,7 @@ bool WaylandDisplay::Run() {
9051051
};
9061052

9071053
rv = ppoll(&fds[0], std::size(fds), &ts, nullptr);
908-
} while (rv == -1 && rv == EINTR);
1054+
} while (rv == -1 && errno == EINTR);
9091055

9101056
if (rv == -1) {
9111057
printf("ERROR: ppoll returned -1 (errno: %d)\n", errno);
@@ -957,6 +1103,18 @@ bool WaylandDisplay::Run() {
9571103
wl_display_cancel_read(display_);
9581104
}
9591105

1106+
if (fds[3].revents & POLLIN) {
1107+
uint64_t result;
1108+
do {
1109+
rv = read(fds[3].fd, &result, sizeof result);
1110+
} while (rv == -1 && errno == EINTR);
1111+
if (rv == -1) {
1112+
dbgE(MEMWATCHTAG "problems reading event fd, errno=%d\n", errno);
1113+
} else {
1114+
HandleMemoryWatcherEvent();
1115+
}
1116+
}
1117+
9601118
break;
9611119
} while (true);
9621120

src/wayland_display.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,26 @@ class WaylandDisplay {
8585

8686
FlutterEngine engine_ = nullptr;
8787

88+
struct {
89+
std::vector<long> levels;
90+
long current_level = 0;
91+
int memory_file_fd = -1;
92+
uint64_t last_warning_sent_ns = 0;
93+
int event_fd = -1;
94+
} memory_watcher_;
95+
8896
bool SetupEGL();
8997

9098
bool SetupEngine(const std::string &bundle_path, const std::vector<std::string> &command_line_args);
9199

92100
bool StopRunning();
93101

102+
void SetupMemoryWatcher();
103+
104+
void HandleMemoryWatcherEvent();
105+
106+
void CleanupMemoryWatcher();
107+
94108
// key repeat related
95109
struct {
96110
int32_t repeat_delay_ms_ = 400;

0 commit comments

Comments
 (0)