--- /dev/null
+/*
+ * 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)