cpu-boosting-monitor: Add cpu PSI monitor module 23/297123/5
authorUnsung Lee <unsung.lee@samsung.com>
Wed, 9 Aug 2023 03:23:25 +0000 (12:23 +0900)
committerUnsung Lee <unsung.lee@samsung.com>
Mon, 28 Aug 2023 01:49:13 +0000 (10:49 +0900)
Add cpu PSI monitor to detect cpu contention.
cpu PSI monitor code works very similar with lowmem PSI monitor (memory PSI).
However, CPU PSI is only focusing on 'some' events.
This is because, global CPU PSI information (i.e., /proc/pressure/cpu)
always has zero (i.e., meaningless value) for 'full'.

Change-Id: Ie4d06bda0a57d7918abc1dddd23eb09dfffce053
Signed-off-by: Unsung Lee <unsung.lee@samsung.com>
src/CMakeLists.txt
src/resource-optimizer/cpu/cpu-boosting-monitor-psi.c [new file with mode: 0644]

index 7dc00aa..9613cf1 100644 (file)
@@ -86,7 +86,8 @@ IF("${CPU_THROTTLING_MODULE}" STREQUAL "ON")
 ENDIF()
 
 IF("${CPU_BOOSTING_MODULE}" STREQUAL "ON")
-  FILE(GLOB FILES "${CPU_RESOURCE_OPTIMIZER_SOURCE_DIR}/cpu-boosting.c")
+  FILE(GLOB FILES "${CPU_RESOURCE_OPTIMIZER_SOURCE_DIR}/cpu-boosting.c"
+                 "${CPU_RESOURCE_OPTIMIZER_SOURCE_DIR}/cpu-boosting-monitor-psi.c")
   FOREACH(FILE ${FILES})
       SET(SOURCES ${SOURCES} ${FILE})
   ENDFOREACH()
diff --git a/src/resource-optimizer/cpu/cpu-boosting-monitor-psi.c b/src/resource-optimizer/cpu/cpu-boosting-monitor-psi.c
new file mode 100644 (file)
index 0000000..e3115a8
--- /dev/null
@@ -0,0 +1,391 @@
+/**
+ * resourced
+ *
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file cpu-boosting-monitor-psi.c
+ * @desc Provides monitor functionalities to detect cpu contention using PSI
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <pthread.h>
+
+#include "procfs.h"
+#include "trace.h"
+#include "module.h"
+
+#define EPOLL_LISTENER_MAX_EVENTS              100
+#define BUFF_MAX                               255
+
+#define PSI_PATH                               "/proc/pressure"
+#define PSI_CPU_PATH                           PSI_PATH"/cpu"
+#define PSI_TYPE_SOME                          "some"
+
+enum psi_cpu_level {
+       PSI_CPU_LEVEL_UNKNOWN,
+       PSI_CPU_LEVEL_LOW,
+       PSI_CPU_LEVEL_MEDIUM,
+       PSI_CPU_LEVEL_HIGH,
+       PSI_CPU_LEVEL_MAX,
+};
+
+struct psi_cpu_monitor_info {
+       const int psi_cpu_level;
+       const char *psi_cpu_level_str;
+       const char *psi_type;
+       const int stall_us;
+       const int window_us;
+       int fd;
+};
+
+typedef void *(*epoll_event_handler)(void *data);
+
+struct epoll_event_data {
+       epoll_event_handler handler;
+       void *data;
+};
+
+static int g_psi_monitor_epoll_fd = -1;
+static pthread_t g_psi_monitor_thread;
+static int g_psi_monitor_thread_destroy_event_fd = -1;
+static enum psi_cpu_level g_current_psi_cpu_level = PSI_CPU_LEVEL_UNKNOWN;
+
+static void assert_psi_cpu_level(int psi_cpu_level)
+{
+       switch (psi_cpu_level) {
+       case PSI_CPU_LEVEL_LOW:
+       case PSI_CPU_LEVEL_MEDIUM:
+       case PSI_CPU_LEVEL_HIGH:
+               return;
+       default:
+               _E("Invalid PSI cpu level: %d", psi_cpu_level);
+               assert(0);
+       }
+}
+
+static void *psi_cpu_monitor_handler(struct psi_cpu_monitor_info *info)
+{
+       assert_psi_cpu_level(info->psi_cpu_level);
+
+       if (g_current_psi_cpu_level < info->psi_cpu_level)
+               g_current_psi_cpu_level = info->psi_cpu_level;
+
+       return NULL;
+}
+
+static void *psi_monitor_thread_destroy_handler(int *efd)
+{
+       eventfd_t dummy = 0;
+       eventfd_read(*efd, &dummy);
+
+       pthread_exit(NULL);
+}
+
+static struct epoll_event_data g_psi_monitor_thread_destroy_event_data = {
+       .handler = (epoll_event_handler)psi_monitor_thread_destroy_handler,
+       .data = &g_psi_monitor_thread_destroy_event_fd,
+};
+
+static int register_psi_event_epoll(struct epoll_event_data *event_data)
+{
+       char trigger_description[BUFF_MAX] = { 0 };
+       int trigger_description_len = -1;
+       int ret = 0;
+       int fd = -1;
+       struct psi_cpu_monitor_info *info = NULL;
+       struct epoll_event event;
+
+       assert(g_psi_monitor_epoll_fd >= 0);
+
+       assert(event_data);
+       info = event_data->data;
+       assert(info);
+       assert_psi_cpu_level(info->psi_cpu_level);
+
+       fd = open(PSI_CPU_PATH, O_RDWR | O_NONBLOCK);
+       if (fd < 0) {
+               _E("Failed to open psi node: path=%s, errno=%d",
+                                       PSI_CPU_PATH, errno);
+               goto error_1;
+       }
+       info->fd = fd;
+
+       event.events = EPOLLPRI;
+       event.data.ptr = (void *)event_data;
+
+       trigger_description_len = snprintf(trigger_description, BUFF_MAX,
+                                                       "%s %d %d",
+                                                       info->psi_type,
+                                                       info->stall_us,
+                                                       info->window_us);
+       if (trigger_description_len < 0) {
+               _E("Failed to write PSI trigger description: returned value=%d",
+                                               trigger_description_len);
+               goto error_2;
+       }
+       if (trigger_description_len >= BUFF_MAX) {
+               _E("Failed to write PSI trigger description: buffer is not enough");
+               goto error_2;
+       }
+
+       ret = write(fd, trigger_description, trigger_description_len + 1);
+       if (ret < 0) {
+               _E("Failed to write PSI trigger description(\"%s\"): errno=%d",
+                                               trigger_description, errno);
+               goto error_2;
+       }
+
+       ret = epoll_ctl(g_psi_monitor_epoll_fd, EPOLL_CTL_ADD, fd, &event);
+       if (ret < 0) {
+               _E("Failed to add epoll: errno=%d", errno);
+               goto error_2;
+       }
+
+       _I("PSI event registered(%s %d %d) as %s psi_cpu_level", info->psi_type,
+                                                       info->stall_us,
+                                                       info->window_us,
+                                                       info->psi_cpu_level_str);
+
+       return 0;
+
+error_2:
+       close(fd);
+       info->fd = -1;
+error_1:
+       _E("Failed to register psi event: psi_cpu_level=%s", info->psi_cpu_level_str);
+
+       return RESOURCED_ERROR_FAIL;
+}
+
+static void unregister_psi_event_epoll(struct epoll_event_data *event_data)
+{
+       struct psi_cpu_monitor_info *info = NULL;
+
+       assert(event_data);
+
+       info = event_data->data;
+       assert(info);
+       assert_psi_cpu_level(info->psi_cpu_level);
+
+       if (info->fd < 0)
+               return;
+
+       epoll_ctl(g_psi_monitor_epoll_fd, EPOLL_CTL_DEL, info->fd, NULL);
+       close(info->fd);
+       info->fd = -1;
+}
+
+static void raise_cpu_contention_event(void)
+{
+       switch (g_current_psi_cpu_level) {
+       case PSI_CPU_LEVEL_LOW:
+       case PSI_CPU_LEVEL_MEDIUM:
+       case PSI_CPU_LEVEL_HIGH:
+               break;
+       default:
+               return;
+       }
+}
+
+static void *psi_monitor_thread_worker(void *data)
+{
+       struct epoll_event events[EPOLL_LISTENER_MAX_EVENTS];
+       int events_num = 0;
+
+       while (1) {
+               events_num = epoll_wait(g_psi_monitor_epoll_fd, events,
+                               EPOLL_LISTENER_MAX_EVENTS, -1);
+
+               g_current_psi_cpu_level = PSI_CPU_LEVEL_UNKNOWN;
+               for (int i = 0; i < events_num; ++i) {
+                       if (events[i].events & (EPOLLERR | EPOLLHUP))
+                               continue;
+
+                       struct epoll_event_data *event_data = events[i].data.ptr;
+                       event_data->handler(event_data->data);
+               }
+
+               raise_cpu_contention_event();
+       }
+
+       pthread_exit(NULL);
+}
+
+static int create_psi_monitor_thread(void)
+{
+       int ret = 0;
+       struct epoll_event event;
+
+       g_psi_monitor_thread_destroy_event_fd = eventfd(0, EFD_CLOEXEC);
+       if (g_psi_monitor_thread_destroy_event_fd < 0) {
+               _E("Failed to create eventfd for thread: errno=%d", errno);
+               return RESOURCED_ERROR_FAIL;
+       }
+       g_psi_monitor_thread_destroy_event_data.data =
+                       (void *)(&g_psi_monitor_thread_destroy_event_fd);
+
+       event.events = EPOLLIN;
+       event.data.ptr = (void *)(&g_psi_monitor_thread_destroy_event_data);
+
+       ret = epoll_ctl(g_psi_monitor_epoll_fd, EPOLL_CTL_ADD,
+                       g_psi_monitor_thread_destroy_event_fd, &event);
+       if (ret != 0) {
+               close(g_psi_monitor_thread_destroy_event_fd);
+               g_psi_monitor_thread_destroy_event_fd = -1;
+               _E("Failed to add eventfd for thread: errno=%d", errno);
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       ret = pthread_create(&g_psi_monitor_thread, NULL, psi_monitor_thread_worker, NULL);
+       if (ret != 0) {
+               epoll_ctl(g_psi_monitor_epoll_fd, EPOLL_CTL_DEL,
+                               g_psi_monitor_thread_destroy_event_fd, NULL);
+               close(g_psi_monitor_thread_destroy_event_fd);
+               g_psi_monitor_thread_destroy_event_fd = -1;
+               _E("Failed to create psi monitor thread: errno=%d", errno);
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       return RESOURCED_ERROR_NONE;
+}
+
+static void destroy_psi_monitor_thread(void)
+{
+       eventfd_write(g_psi_monitor_thread_destroy_event_fd, 1);
+       pthread_join(g_psi_monitor_thread, NULL);
+
+       epoll_ctl(g_psi_monitor_epoll_fd, EPOLL_CTL_DEL,
+                       g_psi_monitor_thread_destroy_event_fd, NULL);
+       close(g_psi_monitor_thread_destroy_event_fd);
+       g_psi_monitor_thread_destroy_event_fd = -1;
+}
+
+static struct psi_cpu_monitor_info g_psi_cpu_monitor_info_list[PSI_CPU_LEVEL_MAX] = {
+       [PSI_CPU_LEVEL_LOW] = {
+               .psi_cpu_level          = PSI_CPU_LEVEL_LOW,
+               .psi_cpu_level_str      = "low",
+               .psi_type               = PSI_TYPE_SOME,
+               .stall_us               = 70000,
+               .window_us              = 1000000,
+               .fd                     = -1,
+       },
+       [PSI_CPU_LEVEL_MEDIUM] = {
+               .psi_cpu_level          = PSI_CPU_LEVEL_MEDIUM,
+               .psi_cpu_level_str      = "medium",
+               .psi_type               = PSI_TYPE_SOME,
+               .stall_us               = 100000,
+               .window_us              = 1000000,
+               .fd                     = -1,
+       },
+       [PSI_CPU_LEVEL_HIGH] = {
+               .psi_cpu_level          = PSI_CPU_LEVEL_HIGH,
+               .psi_cpu_level_str      = "high",
+               .psi_type               = PSI_TYPE_SOME,
+               .stall_us               = 200000,
+               .window_us              = 1000000,
+               .fd                     = -1,
+       },
+};
+
+static struct epoll_event_data g_psi_event_datas[PSI_CPU_LEVEL_MAX] = {
+       [PSI_CPU_LEVEL_LOW] = {
+               .handler = (epoll_event_handler)psi_cpu_monitor_handler,
+               .data = &g_psi_cpu_monitor_info_list[PSI_CPU_LEVEL_LOW],
+       },
+       [PSI_CPU_LEVEL_MEDIUM] = {
+               .handler = (epoll_event_handler)psi_cpu_monitor_handler,
+               .data = &g_psi_cpu_monitor_info_list[PSI_CPU_LEVEL_MEDIUM],
+       },
+       [PSI_CPU_LEVEL_HIGH] = {
+               .handler = (epoll_event_handler)psi_cpu_monitor_handler,
+               .data = &g_psi_cpu_monitor_info_list[PSI_CPU_LEVEL_HIGH],
+       },
+};
+
+static int register_psi_events(void)
+{
+       int psi_cpu_level = -1;
+
+       g_psi_monitor_epoll_fd = epoll_create(1);
+       if (g_psi_monitor_epoll_fd < 0) {
+               _E("Failed to create epoll fd: %d", -errno);
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       for (psi_cpu_level = PSI_CPU_LEVEL_UNKNOWN + 1;
+                       psi_cpu_level < PSI_CPU_LEVEL_MAX; ++psi_cpu_level) {
+               if (register_psi_event_epoll(&g_psi_event_datas[psi_cpu_level]) < 0)
+                       break;
+       }
+
+       if (psi_cpu_level < PSI_CPU_LEVEL_MAX) {
+               for (psi_cpu_level = psi_cpu_level - 1;
+                               psi_cpu_level > PSI_CPU_LEVEL_UNKNOWN; --psi_cpu_level)
+                       unregister_psi_event_epoll(&g_psi_event_datas[psi_cpu_level]);
+               close(g_psi_monitor_epoll_fd);
+               g_psi_monitor_epoll_fd = -1;
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       return RESOURCED_ERROR_NONE;
+}
+
+static void unregister_psi_events(void)
+{
+       for (int psi_cpu_level = PSI_CPU_LEVEL_UNKNOWN + 1;
+                       psi_cpu_level < PSI_CPU_LEVEL_MAX; ++psi_cpu_level)
+               unregister_psi_event_epoll(&g_psi_event_datas[psi_cpu_level]);
+       close(g_psi_monitor_epoll_fd);
+       g_psi_monitor_epoll_fd = -1;
+}
+
+static int cpu_boosting_monitor_psi_initialize(void *data)
+{
+       if (register_psi_events() != RESOURCED_ERROR_NONE) {
+               _E("Failed to register psi fds to epoll fd");
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       if (create_psi_monitor_thread() != RESOURCED_ERROR_NONE) {
+               _E("Failed to create psi monitor thread");
+               unregister_psi_events();
+               return RESOURCED_ERROR_FAIL;
+       }
+
+       return RESOURCED_ERROR_NONE;
+}
+
+static int cpu_boosting_monitor_psi_finalize(void *data)
+{
+       destroy_psi_monitor_thread();
+       unregister_psi_events();
+
+       return RESOURCED_ERROR_NONE;
+}
+
+static struct module_ops g_cpu_boosting_monitor_psi_modules_ops = {
+       .priority       = MODULE_PRIORITY_INITIAL,
+       .name           = "cpu-boosting-monitor-psi",
+       .init           = cpu_boosting_monitor_psi_initialize,
+       .exit           = cpu_boosting_monitor_psi_finalize,
+};
+
+MODULE_REGISTER(&g_cpu_boosting_monitor_psi_modules_ops)