#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)
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;
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) {
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)
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",
.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;
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;
}
},
};
+#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;
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));
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 */
}
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,
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);
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;
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);