resource: Add process resource driver 56/271356/2
authorDongwoo Lee <dwoo08.lee@samsung.com>
Fri, 4 Feb 2022 06:54:46 +0000 (15:54 +0900)
committerDongwoo Lee <dwoo08.lee@samsung.com>
Mon, 21 Feb 2022 04:47:23 +0000 (13:47 +0900)
Process resource driver provides information about process using
procfs and taskstats.

[Detailed description of added system attributes]
PROCESS_CPU_UTIL:  It shows cpu usage including its all threads.
PROCESS_MEM_VIRT:  It shows all memory that the process can
access, including memory that is swapped out,
memory that is allocated, but not used, and
memory that is from shared libraries.
PROCESS_MEM_RSS:  It shows how much memory is allocated to that
process and is in RAM.
PROCESS_DISK_READ: It shows acutal disk read bytes per second.
PROCESS_DISK_WRITE: It shows acutal disk write bytes per second.

Change-Id: Iab4a76334fc786f068cbee396f3896184cbfff47
Signed-off-by: Dongwoo Lee <dwoo08.lee@samsung.com>
CMakeLists.txt
lib/tmonitor/tmonitor.h
packaging/pass.spec
src/resource/resource-process.c [new file with mode: 0644]

index 9cc70e7..9a04a81 100644 (file)
@@ -47,6 +47,7 @@ SET(SRCS
        src/resource/resource-display.c
        src/resource/resource-system.c
        src/resource/resource-battery.c
+       src/resource/resource-process.c
        src/monitor/monitor.c
        src/monitor/monitor-thread.c
        src/monitor/monitor-command.c
@@ -65,6 +66,8 @@ SET(PKG_MODULES
        gio-unix-2.0
        libudev
        libsystemd
+       libnl-3.0
+       libnl-genl-3.0
        json-c
        hal-api-power
 )
index d4d2fb1..f06e54b 100644 (file)
@@ -90,6 +90,12 @@ extern "C" {
 #define SYSTEM_POSSIBLE_CPU            BIT(6)
 #define SYSTEM_ONLINE_CPU              BIT(7)
 
+#define PROCESS_CPU_UTIL               BIT(0)
+#define PROCESS_MEM_VIRT               BIT(1)
+#define PROCESS_MEM_RSS                        BIT(2)
+#define PROCESS_DISK_READ              BIT(3)
+#define PROCESS_DISK_WRITE             BIT(4)
+
 /**
  * @brief Initialize the tizen monitor
  * @param[in] Timer period (unit: millisecond, minimum value is 100ms)
index f21d2b0..0a5aa54 100644 (file)
@@ -22,6 +22,7 @@ BuildRequires:  pkgconfig(gio-unix-2.0)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(gmock)
 BuildRequires:  pkgconfig(libudev)
+BuildRequires:  pkgconfig(libnl-3.0)
 BuildRequires:  pkgconfig(libsystemd)
 BuildRequires:  pkgconfig(json-c)
 BuildRequires:  pkgconfig(hal-api-power)
diff --git a/src/resource/resource-process.c b/src/resource/resource-process.c
new file mode 100644 (file)
index 0000000..acdb542
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * PASS (Power Aware System Service) - Process Resource Driver
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ */
+
+#include <unistd.h>
+#include <linux/taskstats.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+
+#include <util/log.h>
+#include <util/resource.h>
+#include <util/kernel.h>
+
+#include <tmonitor/tmonitor.h>
+
+struct process_context {
+       pid_t pid;
+       struct taskstats prev;
+       struct taskstats curr;
+
+       u_int64_t prev_total_time;
+       double cpu_period;
+       int online_cpu;
+};
+
+static long jiffy;
+
+static int process_get_cpu_util(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void **data)
+{
+       struct process_context *ctx;
+       struct taskstats *prev, *curr;
+       double util = 0.0;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+       prev = &ctx->prev;
+       curr = &ctx->curr;
+
+       if (ctx->cpu_period >= 1E-6) {
+               util = (double)(curr->ac_utime + curr->ac_stime);
+               util -= (double)(prev->ac_utime + prev->ac_stime);
+               util *= (jiffy / (ctx->cpu_period * 10.0));
+
+               /*
+                * To obtain precision after converting types between double and
+                * void pointer, utilization increased 1000 times (0~100000), so
+                * value should be divided by 1000 at user-side.
+                */
+               util = min(util, 100000.0);
+       }
+
+       *data = (void *)(intptr_t)(int)util;
+
+       return 0;
+}
+
+static int process_get_mem_virt(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void **data)
+{
+       struct process_context *ctx;
+       struct taskstats *curr;
+       double virt;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+       curr = &ctx->curr;
+
+       virt = (double)curr->virtmem / (double)curr->ac_stime;
+       virt *= 1024.0 * 1000.0;
+
+       *data = (void *)(uintptr_t)(int)virt;
+
+       return 0;
+}
+
+static int process_get_mem_rss(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void **data)
+{
+       struct process_context *ctx;
+       struct taskstats *curr;
+       double rss;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+       curr = &ctx->curr;
+
+       rss = (double)curr->coremem / (double)curr->ac_stime;
+       rss *= 1024.0 * 1000.0;
+
+       *data = (void *)(intptr_t)(int)rss;
+
+       return 0;
+}
+
+static int process_get_disk_read(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void **data)
+{
+       struct process_context *ctx;
+       struct taskstats *prev, *curr;
+       u_int64_t period;
+       double bps = 0.0;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+       prev = &ctx->prev;
+       curr = &ctx->curr;
+
+       period = curr->ac_etime - prev->ac_etime;
+       bps = (double)(curr->read_bytes - prev->read_bytes) * 1000000 / period;
+
+       *data = (void *)(intptr_t)(int)bps;
+
+       return 0;
+}
+
+static int process_get_disk_write(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void **data)
+{
+       struct process_context *ctx;
+       struct taskstats *prev, *curr;
+       u_int64_t period;
+       double bps = 0.0;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+       prev = &ctx->prev;
+       curr = &ctx->curr;
+
+       period = curr->ac_etime - prev->ac_etime;
+
+       bps = (double)(curr->write_bytes - prev->write_bytes) * 1000000 / period;
+
+       *data = (void *)(intptr_t)(int)bps;
+
+       return 0;
+}
+
+static const struct resource_attribute process_attrs[] = {
+       {
+               .name   = "PROCESS_CPU_UTIL",
+               .id     = PROCESS_CPU_UTIL,
+               .type   = DATA_TYPE_INT,
+               .ops    = {
+                       .get = process_get_cpu_util,
+               },
+       },
+       {
+               .name   = "PROCESS_MEM_VIRT",
+               .id     = PROCESS_MEM_VIRT,
+               .type   = DATA_TYPE_INT,
+               .ops    = {
+                       .get = process_get_mem_virt,
+               },
+       },
+       {
+               .name   = "PROCESS_MEM_RSS",
+               .id     = PROCESS_MEM_RSS,
+               .type   = DATA_TYPE_INT,
+               .ops    = {
+                       .get = process_get_mem_rss,
+               },
+       },
+       {
+               .name   = "PROCESS_DISK_READ",
+               .id     = PROCESS_DISK_READ,
+               .type   = DATA_TYPE_INT,
+               .ops    = {
+                       .get = process_get_disk_read,
+               },
+       },
+       {
+               .name   = "PROCESS_DISK_WRITE",
+               .id     = PROCESS_DISK_WRITE,
+               .type   = DATA_TYPE_INT,
+               .ops    = {
+                       .get = process_get_disk_write,
+               },
+       },
+};
+
+#define saturatingSub(a, b) (a > b ? a - b : 0)
+
+static u_int64_t get_total_cpu_time(void)
+{
+       struct cpu_stat cpu_stat;
+       u_int64_t total_time;
+       int ret;
+
+       ret = kernel_get_total_cpu_stat(&cpu_stat);
+       if (ret < 0) {
+               _E("failed to get cpu stat");
+               return 0;
+       };
+
+       total_time = cpu_stat.user + cpu_stat.system + cpu_stat.nice + cpu_stat.idle;
+       total_time += cpu_stat.wait + cpu_stat.hard_irq + cpu_stat.soft_irq;
+
+       return total_time;
+}
+
+static int parse_aggregate_task_stats(struct nlattr *attr, int attr_size,
+                                       struct taskstats *stats)
+{
+       nla_for_each_attr(attr, attr, attr_size, attr_size) {
+               switch (attr->nla_type) {
+               case TASKSTATS_TYPE_STATS:
+                       nla_memcpy(stats, attr, sizeof(struct taskstats));
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static int parse_task_stats(struct nl_msg *msg, void *arg)
+{
+       struct genlmsghdr *hdr = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+       struct nlattr *attr = genlmsg_attrdata(hdr, 0);
+       int remaining = genlmsg_attrlen(hdr, 0);
+       int ret;
+
+       nla_for_each_attr(attr, attr, remaining, remaining) {
+               switch (attr->nla_type) {
+               case TASKSTATS_TYPE_AGGR_PID:
+               case TASKSTATS_TYPE_AGGR_TGID:
+                       ret = parse_aggregate_task_stats(nla_data(attr),
+                                                  nla_len(attr), arg);
+                       if (ret < 0) {
+                               _E("failed to parse netlink message\n");
+                               return NL_STOP;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return NL_STOP;
+}
+
+static int print_receive_error(struct sockaddr_nl *address,
+                               struct nlmsgerr *error, void *arg)
+{
+       return NL_STOP;
+}
+
+static int query_taskstats(struct taskstats *stats, int cmd_type, pid_t pid)
+{
+       struct nl_sock *sock;
+       struct nl_msg *msg;
+       struct nl_cb *cb;
+       int ret;
+
+       sock = nl_socket_alloc();
+       if (!sock)
+               return -ENOMEM;
+
+       ret = genl_connect(sock);
+       if (ret < 0)
+               goto err_free_sock;
+
+       ret = genl_ctrl_resolve(sock, TASKSTATS_GENL_NAME);
+       if (ret < 0)
+               goto err_genl_close;
+
+       msg = nlmsg_alloc();
+       if (!msg) {
+               ret = -ENOMEM;
+               goto err_genl_close;
+       }
+
+       genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ret, 0, 0, TASKSTATS_CMD_GET, TASKSTATS_VERSION);
+
+       nla_put_u32(msg, cmd_type, pid);
+
+       ret = nl_send_auto_complete(sock, msg);
+       nlmsg_free(msg);
+       if (ret < 0)
+               goto err_genl_close;
+
+       cb = nl_cb_get(nl_cb_alloc(NL_CB_CUSTOM));
+       nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, &parse_task_stats, stats);
+       nl_cb_err(cb, NL_CB_CUSTOM, &print_receive_error, NULL);
+
+       ret = nl_recvmsgs(sock, cb);
+       nl_cb_put(cb);
+       if (ret < 0)
+               goto err_genl_close;
+
+       nl_close(sock);
+       nl_socket_free(sock);
+
+       return 0;
+
+err_genl_close:
+       nl_close(sock);
+err_free_sock:
+       nl_socket_free(sock);
+
+       return ret;
+}
+
+static int update_taskstats(struct process_context *ctx)
+{
+       struct taskstats stats, *curr = &ctx->curr;
+       struct dirent *task_entry;
+       DIR *task_dir;
+       char task_dir_path[BUFF_MAX];
+       pid_t pid;
+       int ret;
+
+       memset(curr, 0, sizeof(struct taskstats));
+
+       sprintf(task_dir_path, "/proc/%d/task/", ctx->pid);
+
+       task_dir = opendir(task_dir_path);
+       if (!task_dir)
+               return -ESRCH;
+
+       while ((task_entry = readdir(task_dir)) != NULL) {
+               const char *name = task_entry->d_name;
+
+               if (task_entry->d_type != DT_DIR && task_entry->d_type != DT_UNKNOWN)
+                       continue;
+
+               if (name[0] < '0' || name[0] > '9')
+                       continue;
+
+               pid = strtoul(name, NULL, 10);
+
+               ret = query_taskstats(&stats, TASKSTATS_CMD_ATTR_PID, pid);
+               if (ret < 0)
+                       /* threads can be removed before get taskstats */
+                       continue;
+
+               curr->ac_utime += stats.ac_utime;
+               curr->ac_stime += stats.ac_stime;
+               curr->ac_etime += stats.ac_etime;
+               curr->virtmem += stats.virtmem;
+               curr->coremem += stats.coremem;
+               curr->read_bytes += stats.read_bytes;
+               curr->write_bytes += stats.write_bytes;
+       }
+
+       closedir(task_dir);
+
+       return 0;
+}
+
+static int process_prepare_update(struct resource *res)
+{
+       struct process_context *ctx = res->priv;
+       u_int64_t total_time;
+       int ret, online;
+
+       memcpy(&ctx->prev, &ctx->curr, sizeof(struct taskstats));
+
+       ret = update_taskstats(ctx);
+       if (ret < 0)
+               return ret;
+
+       online = kernel_get_online_cpu_num();
+       total_time = get_total_cpu_time();
+
+       ctx->online_cpu = online;
+       ctx->cpu_period = (double)saturatingSub(total_time, ctx->prev_total_time) / online;
+       ctx->prev_total_time = total_time;
+
+       return 0;
+}
+
+static int process_init(struct resource *res)
+{
+       struct process_context *ctx;
+       int ret;
+
+       if (jiffy == 0) {
+               /* get system USER_HZ at once */
+               jiffy = sysconf(_SC_CLK_TCK);
+               if (jiffy < 0)
+                       return -EINVAL;
+       }
+
+       ctx = calloc(1, sizeof(struct process_context));
+       if (!ctx)
+               return -ENOMEM;
+
+       ctx->prev_total_time = get_total_cpu_time();
+       ctx->pid = (pid_t)(intptr_t)res->user_data;
+
+       /* update initial status */
+       ret = update_taskstats(ctx);
+       if (ret < 0) {
+               free(ctx);
+               return -EINVAL;
+       }
+
+       res->priv = ctx;
+
+       return 0;
+}
+
+static void process_exit(struct resource *res)
+{
+       struct process_context *ctx;
+
+       if (res->priv) {
+               ctx = res->priv;
+               free(ctx);
+               res->priv = NULL;
+       }
+}
+
+static const struct resource_driver process_resource_driver = {
+       .name           = "PROCESS",
+       .type           = RESOURCE_TYPE_PROCESS,
+       .attrs          = process_attrs,
+       .flags          = RESOURCE_DRIVER_NO_DEVICE,
+       .num_attrs      = ARRAY_SIZE(process_attrs),
+       .ops = {
+               .init = process_init,
+               .exit = process_exit,
+               .prepare_update = process_prepare_update,
+       },
+};
+RESOURCE_DRIVER_REGISTER(&process_resource_driver)