resource: process-group: Add aggregated attributes for accounting stats 57/273457/2 accepted/tizen/unified/20220412.100309 submit/tizen/20220408.030842
authorDongwoo Lee <dwoo08.lee@samsung.com>
Sun, 3 Apr 2022 14:14:22 +0000 (23:14 +0900)
committerDongwoo Lee <dwoo08.lee@samsung.com>
Thu, 7 Apr 2022 01:40:28 +0000 (10:40 +0900)
To provide aggregated accounting information about the process group
which includes target process and its child at a driver level, the
structure managing the current process group is changed from list to
hash, and it is preserved until root target is changed or a prcess in
a group is terminated to maintain previous stats. Thus, the clients can
retrieved aggregated accounting stats without create the individual
process driver. Newly added attributes are as follows:
 - PROCESS_GROUP_ATTR_CPU_UTIL
 - PROCESS_GROUP_ATTR_DISK_READ_BPS
 - PROCESS_GROUP_ATTR_DISK_WRITE_BPS
 - PROCESS_GROUP_ATTR_MEM_VIRT
 - PROCESS_GROUP_ATTR_MEM_RSS

Change-Id: I967da6110fb8568383c2ba096d599f51ce8e54e2
Signed-off-by: Dongwoo Lee <dwoo08.lee@samsung.com>
lib/resource-monitor/resource-monitor.h
src/resource/resource-process-group.c

index 3b1b6cb..515ec10 100644 (file)
@@ -127,6 +127,11 @@ extern "C" {
 /* Process List Resource */
 #define PROCESS_GROUP_ATTR_PID_LIST            BIT(0)  /* DATA_TYPE_ARRAY(INT) */
 #define PROCESS_GROUP_ATTR_COMM_LIST           BIT(1)  /* DATA_TYPE_ARRAY(STRING) */
+#define PROCESS_GROUP_ATTR_CPU_UTIL            BIT(2)  /* DATA_TYPE_DOUBLE */
+#define PROCESS_GROUP_ATTR_DISK_READ_BPS       BIT(3)  /* DATA_TYPE_UINT */
+#define PROCESS_GROUP_ATTR_DISK_WRITE_BPS      BIT(4)  /* DATA_TYPE_UINT */
+#define PROCESS_GROUP_ATTR_MEM_VIRT            BIT(5)  /* DATA_TYPE_UINT64 */
+#define PROCESS_GROUP_ATTR_MEM_RSS             BIT(6)  /* DATA_TYPE_UINT64 */
 
 #define PROCESS_GROUP_CTRL_ROOT_PID            BIT(0)
 
index 31bb359..959ef43 100644 (file)
 #define PROC_DIR_PATH  "/proc/"
 
 struct process_info_node {
+       pid_t tgid;
+       struct taskstats *prev;
        struct taskstats *stats;
        struct process_info_node *parent;
 };
 
 struct process_group_context {
-       struct taskstats root_stats;
-       GPtrArray *pi_list;
+       pid_t pid;
+       GHashTable *pi_map;
+       struct {
+               double cpu_util;
+               u_int64_t mem_rss;
+               u_int64_t mem_virt;
+               u_int32_t disk_rbps;
+               u_int32_t disk_wbps;
+       } info;
+       u_int64_t prev_total_time;
 };
 
+static u_int64_t total_memory;
+static long jiffy;
+
 static int process_group_get_pid_list(const struct resource *res,
                                const struct resource_attribute *attr,
                                void *data)
@@ -42,30 +55,32 @@ static int process_group_get_pid_list(const struct resource *res,
        struct process_group_context *ctx;
        struct array_value *list = data;
        struct process_info_node *node;
+       GHashTableIter iter;
+       gpointer key, value;
        int *data_array;
-       int i;
+       int i = 0;
 
        if (!res || !res->priv || !attr || !data)
                return -EINVAL;
 
        ctx = res->priv;
 
-       if (!ctx->pi_list)
+       if (!ctx->pi_map)
                return -EINVAL;
 
        if (list->data)
                free(list->data);
 
-       data_array = list->data = malloc(sizeof(int) * ctx->pi_list->len);
+       list->type = DATA_TYPE_INT;
+       list->length = g_hash_table_size(ctx->pi_map);
+       data_array = list->data = malloc(sizeof(int) * list->length);
        if (!data_array)
                return -ENOMEM;
 
-       list->type = DATA_TYPE_INT;
-       list->length = ctx->pi_list->len;
-
-       for (i = 0; i < list->length; i++) {
-               node = g_ptr_array_index(ctx->pi_list, i);
-               data_array[i] = node->stats->ac_pid;
+       g_hash_table_iter_init(&iter, ctx->pi_map);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               node = (struct process_info_node *)value;
+               data_array[i++] = node->tgid;
        }
 
        return 0;
@@ -78,15 +93,17 @@ static int process_group_get_comm_list(const struct resource *res,
        struct process_group_context *ctx;
        struct array_value *list = data;
        struct process_info_node *node;
+       GHashTableIter iter;
+       gpointer key, value;
        char **data_array;
-       int i;
+       int i = 0;
 
        if (!res || !res->priv || !attr || !data)
                return -EINVAL;
 
        ctx = res->priv;
 
-       if (!ctx->pi_list)
+       if (!ctx->pi_map)
                return -EINVAL;
 
        if (list->data) {
@@ -96,16 +113,17 @@ static int process_group_get_comm_list(const struct resource *res,
                free(data_array);
        }
 
+       list->type = DATA_TYPE_STRING;
+       list->length = g_hash_table_size(ctx->pi_map);
+
        /* last item will be null for boundary check */
-       data_array = list->data = calloc(ctx->pi_list->len + 1, sizeof(char *));
+       data_array = list->data = calloc(list->length + 1, sizeof(char *));
        if (!data_array)
                return -ENOMEM;
 
-       list->type = DATA_TYPE_STRING;
-       list->length = ctx->pi_list->len;
-
-       for (i = 0; i < list->length; i++) {
-               node = g_ptr_array_index(ctx->pi_list, i);
+       g_hash_table_iter_init(&iter, ctx->pi_map);
+       for (i = 0; g_hash_table_iter_next(&iter, &key, &value); i++) {
+               node = (struct process_info_node *)value;
                data_array[i] = strdup(node->stats->ac_comm);
                if (!data_array[i]) {
                        while (i > 0)
@@ -119,6 +137,87 @@ static int process_group_get_comm_list(const struct resource *res,
        return 0;
 }
 
+static int process_group_get_cpu_util(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void *data)
+{
+       struct process_group_context *ctx;
+       double *util = (double *)data;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+
+       if (!ctx->pi_map || ctx->pid < 0)
+               return -EINVAL;
+
+       *util = ctx->info.cpu_util;
+
+       return 0;
+}
+
+static int process_group_get_mem(const struct resource *res,
+                          const struct resource_attribute *attr,
+                          void *data)
+{
+       struct process_group_context *ctx;
+       u_int64_t *mem = (u_int64_t *)data;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+
+       if (!ctx->pi_map || ctx->pid < 0)
+               return -EINVAL;
+
+       switch (attr->id) {
+       case PROCESS_GROUP_ATTR_MEM_RSS:
+       {
+               *mem = ctx->info.mem_rss;
+               break;
+       }
+       case PROCESS_GROUP_ATTR_MEM_VIRT:
+       {
+               *mem = ctx->info.mem_virt;
+               break;
+       }
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int process_group_get_disk_bps(const struct resource *res,
+                               const struct resource_attribute *attr,
+                               void *data)
+{
+       struct process_group_context *ctx;
+       u_int32_t *bps = (u_int32_t *)data;
+
+       if (!res || !res->priv || !attr || !data)
+               return -EINVAL;
+
+       ctx = res->priv;
+
+       if (!ctx->pi_map || ctx->pid < 0)
+               return -EINVAL;
+
+       switch (attr->id) {
+       case PROCESS_GROUP_ATTR_DISK_READ_BPS:
+               *bps = ctx->info.disk_rbps;
+               break;
+       case PROCESS_GROUP_ATTR_DISK_WRITE_BPS:
+               *bps = ctx->info.disk_wbps;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
 static const struct resource_attribute process_group_attrs[] = {
        {
                .name   = "PROCESS_GROUP_ATTR_PID_LIST",
@@ -138,12 +237,54 @@ static const struct resource_attribute process_group_attrs[] = {
                        .is_supported = resource_attr_supported_always,
                },
        },
+       {
+               .name   = "PROCESS_GROUP_ATTR_CPU_UTIL",
+               .id     = PROCESS_GROUP_ATTR_CPU_UTIL,
+               .type   = DATA_TYPE_DOUBLE,
+               .ops    = {
+                       .get = process_group_get_cpu_util,
+                       .is_supported = resource_attr_supported_always,
+               },
+       }, {
+               .name   = "PROCESS_GROUP_ATTR_DISK_READ_BPS",
+               .id     = PROCESS_GROUP_ATTR_DISK_READ_BPS,
+               .type   = DATA_TYPE_UINT,
+               .ops    = {
+                       .get = process_group_get_disk_bps,
+                       .is_supported = resource_attr_supported_always,
+               },
+       }, {
+               .name   = "PROCESS_GROUP_ATTR_DISK_WRITE_BPS",
+               .id     = PROCESS_GROUP_ATTR_DISK_WRITE_BPS,
+               .type   = DATA_TYPE_UINT,
+               .ops    = {
+                       .get = process_group_get_disk_bps,
+                       .is_supported = resource_attr_supported_always,
+               },
+       }, {
+               .name   = "PROCESS_GROUP_ATTR_MEM_VIRT",
+               .id     = PROCESS_GROUP_ATTR_MEM_VIRT,
+               .type   = DATA_TYPE_UINT64,
+               .ops    = {
+                       .get = process_group_get_mem,
+                       .is_supported = resource_attr_supported_always,
+               },
+       }, {
+               .name   = "PROCESS_GROUP_ATTR_MEM_RSS",
+               .id     = PROCESS_GROUP_ATTR_MEM_RSS,
+               .type   = DATA_TYPE_UINT64,
+               .ops    = {
+                       .get = process_group_get_mem,
+                       .is_supported = resource_attr_supported_always,
+               },
+       },
 };
 
 static int process_group_setup_root_pid(const struct resource *res,
                                const struct resource_control *ctrl,
                                const void *data)
 {
+       struct taskstats stats;
        struct process_group_context *ctx;
        int target_pid;
        int ret;
@@ -156,17 +297,19 @@ static int process_group_setup_root_pid(const struct resource *res,
        target_pid = (int)(intptr_t)data;
 
        if (target_pid < 0) {
-               ctx->root_stats.ac_pid = ctx->root_stats.ac_ppid = -1;
+               ctx->pid = -1;
                return 0;
        }
 
-       ret = kernel_get_process_taskstats(&ctx->root_stats, TASKSTATS_CMD_ATTR_PID, target_pid);
+       ret = kernel_get_process_taskstats(&stats, TASKSTATS_CMD_ATTR_PID, target_pid);
        if (ret < 0) {
                _E("target process pid is not valid");
-               ctx->root_stats.ac_pid = ctx->root_stats.ac_ppid = -1;
+               ctx->pid = -1;
                return ret;
        }
 
+       ctx->pid = stats.ac_pid;
+
        return 0;
 }
 
@@ -180,29 +323,119 @@ static const struct resource_control process_group_ctrls[] = {
        },
 };
 
+#define saturatingSub(a, b) (a > b ? a - b : 0)
+
 static void free_node(void *_node)
 {
        struct process_info_node *node = _node;
 
+       if (node->prev)
+               free(node->prev);
        if (node->stats)
                free(node->stats);
-
        free(node);
 }
 
+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 update_aggr_taskstats(struct process_group_context *ctx)
+{
+       struct process_info_node *node;
+       struct taskstats *prev, *curr;
+       u_int64_t total_time;
+       int online, nproc;
+       double cpu_period;
+       GHashTableIter iter;
+       gpointer key, value;
+       pid_t pid;
+       int ret;
+
+       memset(&ctx->info, 0, sizeof(ctx->info));
+
+       nproc = g_hash_table_size(ctx->pi_map);
+       online = kernel_get_online_cpu_num();
+       total_time = get_total_cpu_time();
+
+       cpu_period = (double)saturatingSub(total_time, ctx->prev_total_time) / online;
+
+       ctx->prev_total_time = total_time;
+
+       g_hash_table_iter_init(&iter, ctx->pi_map);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               node = (struct process_info_node *)value;
+
+               pid = node->tgid;
+               prev = node->prev;
+
+               curr = calloc(1, sizeof(struct taskstats));
+               if (!curr)
+                       return -ENOMEM;
+
+               ret = kernel_get_thread_group_taskstats(curr, pid, true);
+               if (ret < 0) {
+                       free(curr);
+                       g_hash_table_iter_remove(&iter);
+                       continue;
+               }
+
+               if (!prev) {
+                       node->stats = node->prev = curr;
+                       continue;
+               }
+
+               if (cpu_period >= 1E-6) {
+                       double util;
+
+                       util = (double)(curr->ac_utime + curr->ac_stime);
+                       util -= (double)(prev->ac_utime + prev->ac_stime);
+                       util *= (jiffy / (cpu_period * 10000.0));
+                       util = min(util, 100.0);
+
+                       ctx->info.cpu_util += (util / nproc);
+               }
+
+               ctx->info.mem_virt += (curr->virtmem * 1024 * 1024) / curr->ac_stime;
+               ctx->info.mem_rss += (curr->coremem * 1024 * 1024) / curr->ac_stime;
+               ctx->info.disk_rbps += ((curr->read_bytes - prev->read_bytes) * 1000000)
+                                       / (curr->ac_etime - prev->ac_etime);
+               ctx->info.disk_wbps += ((curr->write_bytes - prev->write_bytes) * 1000000)
+                                       / (curr->ac_etime - prev->ac_etime);
+               free(prev);
+               node->prev = curr;
+               node->stats = curr;
+       }
+
+       return 0;
+}
+
 static int process_group_prepare_update(struct resource *res)
 {
        struct process_group_context *ctx;
        struct process_info_node *pnode, *parent_pnode;
        struct dirent *task_entry;
        struct taskstats *stats;
-       GPtrArray *pi_list;
        GHashTableIter iter;
        gpointer key, value;
        GHashTable *process_hash;
        DIR *task_dir;
        int ret = 0;
-       pid_t pid;
+       pid_t tgid;
 
        if (!res || !res->priv)
                return -EINVAL;
@@ -229,9 +462,9 @@ static int process_group_prepare_update(struct resource *res)
                if (name[0] < '0' || name[0] > '9')
                        continue;
 
-               pid = atoi(name);
+               tgid = atoi(name);
 
-               if (pid == 2)
+               if (tgid == 2)
                        continue;
 
                stats = malloc(sizeof(struct taskstats));
@@ -240,7 +473,7 @@ static int process_group_prepare_update(struct resource *res)
                        goto out_free_hash;
                }
 
-               ret = kernel_get_process_taskstats(stats, TASKSTATS_CMD_ATTR_PID, pid);
+               ret = kernel_get_process_taskstats(stats, TASKSTATS_CMD_ATTR_PID, tgid);
                if (ret < 0) {
                        free(stats);
                        continue; /* process might be terminated */
@@ -260,6 +493,7 @@ static int process_group_prepare_update(struct resource *res)
                }
 
                pnode->stats = stats;
+               pnode->tgid = tgid;
 
                if (g_hash_table_contains(process_hash, (gpointer)&pnode->stats->ac_ppid)) {
                        parent_pnode = g_hash_table_lookup(process_hash,
@@ -268,57 +502,62 @@ static int process_group_prepare_update(struct resource *res)
                        pnode->parent = parent_pnode;
                }
 
-               g_hash_table_insert(process_hash, (gpointer)&pnode->stats->ac_pid, (gpointer)pnode);
+               g_hash_table_insert(process_hash, (gpointer)&pnode->tgid, (gpointer)pnode);
        }
 
-       pi_list = g_ptr_array_new_full(100, free_node);
-       if (!pi_list) {
-               ret = -ENOMEM;
-               goto out_free_hash;
+       g_hash_table_iter_init(&iter, ctx->pi_map);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               pnode = (struct process_info_node *)value;
+               if (!g_hash_table_contains(process_hash, key)) {
+                       pnode = (struct process_info_node *)value;
+
+                       g_hash_table_iter_remove(&iter);
+               }
        }
 
-       if (ctx->root_stats.ac_pid < 0) {
+       if (ctx->pid < 0) {
                /* just add all processes into array if parent pid is negative */
                g_hash_table_iter_init(&iter, process_hash);
                while (g_hash_table_iter_next(&iter, &key, &value)) {
-                       pnode = (struct process_info_node *)value;
+                       if (!g_hash_table_contains(ctx->pi_map, key)) {
+                               pnode = (struct process_info_node *)value;
 
-                       g_ptr_array_add(pi_list, pnode);
-                       g_hash_table_iter_steal(&iter);
+                               g_hash_table_insert(ctx->pi_map, key, pnode);
+                               g_hash_table_iter_steal(&iter);
+                       }
                }
        } else {
                g_hash_table_iter_init(&iter, process_hash);
                while (g_hash_table_iter_next(&iter, &key, &value)) {
                        pnode = (struct process_info_node *)value;
 
-                       if (ctx->root_stats.ac_pid == pnode->stats->ac_pid
-                                       || ctx->root_stats.ac_pid == pnode->stats->ac_ppid) {
-
-                               g_ptr_array_add(pi_list, pnode);
-                               g_hash_table_iter_steal(&iter);
-                               continue;
-                       }
-
-                       parent_pnode = pnode->parent;
-                       while (parent_pnode != NULL) {
-                               if (parent_pnode->stats->ac_ppid == ctx->root_stats.ac_pid) {
-                                       g_ptr_array_add(pi_list, pnode);
+                       if (!g_hash_table_contains(ctx->pi_map, key)) {
+                               if (ctx->pid == pnode->stats->ac_pid
+                                               || ctx->pid == pnode->stats->ac_ppid) {
+                                       g_hash_table_insert(ctx->pi_map, key, pnode);
                                        g_hash_table_iter_steal(&iter);
-                                       break;
+                                       continue;
+                               }
+
+                               parent_pnode = pnode->parent;
+                               while (parent_pnode != NULL) {
+                                       if (parent_pnode->stats->ac_ppid == ctx->pid) {
+                                               g_hash_table_insert(ctx->pi_map, key, pnode);
+                                               g_hash_table_iter_steal(&iter);
+                                               break;
+                                       }
+                                       parent_pnode = parent_pnode->parent;
                                }
-                               parent_pnode = parent_pnode->parent;
                        }
                }
-       }
 
-       if (ctx->pi_list) {
-               /* remove previous list */
-               g_ptr_array_free(ctx->pi_list, true);
-               ctx->pi_list = NULL;
+               ret = update_aggr_taskstats(ctx);
+               if (ret < 0) {
+                       ret = -EINVAL;
+                       goto out_free_hash;
+               }
        }
 
-       ctx->pi_list = pi_list;
-
        ret = 0;
 out_free_hash:
        g_hash_table_destroy(process_hash);
@@ -331,13 +570,28 @@ out_close:
 static int process_group_init(struct resource *res)
 {
        struct process_group_context *ctx;
+       int ret;
+
+       if (jiffy == 0) {
+               /* get system USER_HZ at once */
+               jiffy = sysconf(_SC_CLK_TCK);
+               if (jiffy < 0)
+                       return -EINVAL;
+       }
+
+       if (total_memory == 0) {
+               /* get system total memory once at init*/
+               ret = kernel_get_memory_total(&total_memory);
+               if (ret < 0)
+                       return -EINVAL;
+       }
 
        ctx = malloc(sizeof(struct process_group_context));
        if (!ctx)
                return -ENOMEM;
 
-       ctx->root_stats.ac_pid = ctx->root_stats.ac_ppid = -1;
-       ctx->pi_list = NULL;
+       ctx->pid = -1;
+       ctx->pi_map = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, free_node);
 
        res->priv = ctx;
 
@@ -351,10 +605,9 @@ static void process_group_exit(struct resource *res)
        if (res && res->priv) {
                ctx = res->priv;
 
-               if (ctx->pi_list) {
-                       /* remove previous list */
-                       g_ptr_array_free(ctx->pi_list, true);
-                       ctx->pi_list = NULL;
+               if (ctx->pi_map) {
+                       g_hash_table_destroy(ctx->pi_map);
+                       ctx->pi_map = NULL;
                }
 
                free(res->priv);