// SPDX-License-Identifier: GPL-2.0 /* procacct.c * * Demonstrator of fetching resource data on task exit, as a way * to accumulate accurate program resource usage statistics, without * prior identification of the programs. For that, the fields for * device and inode of the program executable binary file are also * extracted in addition to the command string. * * The TGID together with the PID and the AGROUP flag allow * identification of threads in a process and single-threaded processes. * The ac_tgetime field gives proper whole-process walltime. * * Written (changed) by Thomas Orgis, University of Hamburg in 2022 * * This is a cheap derivation (inheriting the style) of getdelays.c: * * Utility to get per-pid and per-tgid delay accounting statistics * Also illustrates usage of the taskstats interface * * Copyright (C) Shailabh Nagar, IBM Corp. 2005 * Copyright (C) Balbir Singh, IBM Corp. 2006 * Copyright (c) Jay Lan, SGI. 2006 */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <poll.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/wait.h> #include <signal.h> #include <linux/genetlink.h> #include <linux/acct.h> #include <linux/taskstats.h> #include <linux/kdev_t.h> /* * Generic macros for dealing with netlink sockets. Might be duplicated * elsewhere. It is recommended that commercial grade applications use * libnl or libnetlink and use the interfaces provided by the library */ #define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN)) #define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN) #define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN)) #define NLA_PAYLOAD(len) (len - NLA_HDRLEN) #define err(code, fmt, arg...) \ do { \ fprintf(stderr, fmt, ##arg); \ exit(code); \ } while (0) int rcvbufsz; char name[100]; int dbg; int print_delays; int print_io_accounting; int print_task_context_switch_counts; #define PRINTF(fmt, arg...) { \ if (dbg) { \ printf(fmt, ##arg); \ } \ } /* Maximum size of response requested or message sent */ #define MAX_MSG_SIZE 1024 /* Maximum number of cpus expected to be specified in a cpumask */ #define MAX_CPUS 32 struct msgtemplate { struct nlmsghdr n; struct genlmsghdr g; char buf[MAX_MSG_SIZE]; }; char cpumask[100+6*MAX_CPUS]; static void usage(void) { fprintf(stderr, "procacct [-v] [-w logfile] [-r bufsize] [-m cpumask]\n"); fprintf(stderr, " -v: debug on\n"); } /* * Create a raw netlink socket and bind */ static int create_nl_socket(int protocol) { int fd; struct sockaddr_nl local; fd = socket(AF_NETLINK, SOCK_RAW, protocol); if (fd < 0) return -1; if (rcvbufsz) if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbufsz, sizeof(rcvbufsz)) < 0) { fprintf(stderr, "Unable to set socket rcv buf size to %d\n", rcvbufsz); goto error; } memset(&local, 0, sizeof(local)); local.nl_family = AF_NETLINK; if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0) goto error; return fd; error: close(fd); return -1; } static int send_cmd(int sd, __u16 nlmsg_type, __u32 nlmsg_pid, __u8 genl_cmd, __u16 nla_type, void *nla_data, int nla_len) { struct nlattr *na; struct sockaddr_nl nladdr; int r, buflen; char *buf; struct msgtemplate msg; msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); msg.n.nlmsg_type = nlmsg_type; msg.n.nlmsg_flags = NLM_F_REQUEST; msg.n.nlmsg_seq = 0; msg.n.nlmsg_pid = nlmsg_pid; msg.g.cmd = genl_cmd; msg.g.version = 0x1; na = (struct nlattr *) GENLMSG_DATA(&msg); na->nla_type = nla_type; na->nla_len = nla_len + 1 + NLA_HDRLEN; memcpy(NLA_DATA(na), nla_data, nla_len); msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len); buf = (char *) &msg; buflen = msg.n.nlmsg_len; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; while ((r = sendto(sd, buf, buflen, 0, (struct sockaddr *) &nladdr, sizeof(nladdr))) < buflen) { if (r > 0) { buf += r; buflen -= r; } else if (errno != EAGAIN) return -1; } return 0; } /* * Probe the controller in genetlink to find the family id * for the TASKSTATS family */ static int get_family_id(int sd) { struct { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } ans; int id = 0, rc; struct nlattr *na; int rep_len; strcpy(name, TASKSTATS_GENL_NAME); rc = send_cmd(sd, GENL_ID_CTRL, getpid(), CTRL_CMD_GETFAMILY, CTRL_ATTR_FAMILY_NAME, (void *)name, strlen(TASKSTATS_GENL_NAME)+1); if (rc < 0) return 0; /* sendto() failure? */ rep_len = recv(sd, &ans, sizeof(ans), 0); if (ans.n.nlmsg_type == NLMSG_ERROR || (rep_len < 0) || !NLMSG_OK((&ans.n), rep_len)) return 0; na = (struct nlattr *) GENLMSG_DATA(&ans); na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len)); if (na->nla_type == CTRL_ATTR_FAMILY_ID) id = *(__u16 *) NLA_DATA(na); return id; } #define average_ms(t, c) (t / 1000000ULL / (c ? c : 1)) static void print_procacct(struct taskstats *t) { /* First letter: T is a mere thread, G the last in a group, U unknown. */ printf( "%c pid=%lu tgid=%lu uid=%lu wall=%llu gwall=%llu cpu=%llu vmpeak=%llu rsspeak=%llu dev=%lu:%lu inode=%llu comm=%s\n" , t->version >= 12 ? (t->ac_flag & AGROUP ? 'P' : 'T') : '?' , (unsigned long)t->ac_pid , (unsigned long)(t->version >= 12 ? t->ac_tgid : 0) , (unsigned long)t->ac_uid , (unsigned long long)t->ac_etime , (unsigned long long)(t->version >= 12 ? t->ac_tgetime : 0) , (unsigned long long)(t->ac_utime+t->ac_stime) , (unsigned long long)t->hiwater_vm , (unsigned long long)t->hiwater_rss , (unsigned long)(t->version >= 12 ? MAJOR(t->ac_exe_dev) : 0) , (unsigned long)(t->version >= 12 ? MINOR(t->ac_exe_dev) : 0) , (unsigned long long)(t->version >= 12 ? t->ac_exe_inode : 0) , t->ac_comm ); } void handle_aggr(int mother, struct nlattr *na, int fd) { int aggr_len = NLA_PAYLOAD(na->nla_len); int len2 = 0; pid_t rtid = 0; na = (struct nlattr *) NLA_DATA(na); while (len2 < aggr_len) { switch (na->nla_type) { case TASKSTATS_TYPE_PID: rtid = *(int *) NLA_DATA(na); PRINTF("PID\t%d\n", rtid); break; case TASKSTATS_TYPE_TGID: rtid = *(int *) NLA_DATA(na); PRINTF("TGID\t%d\n", rtid); break; case TASKSTATS_TYPE_STATS: if (mother == TASKSTATS_TYPE_AGGR_PID) print_procacct((struct taskstats *) NLA_DATA(na)); if (fd) { if (write(fd, NLA_DATA(na), na->nla_len) < 0) err(1, "write error\n"); } break; case TASKSTATS_TYPE_NULL: break; default: fprintf(stderr, "Unknown nested nla_type %d\n", na->nla_type); break; } len2 += NLA_ALIGN(na->nla_len); na = (struct nlattr *)((char *)na + NLA_ALIGN(na->nla_len)); } } int main(int argc, char *argv[]) { int c, rc, rep_len; __u16 id; __u32 mypid; struct nlattr *na; int nl_sd = -1; int len = 0; int fd = 0; int write_file = 0; int maskset = 0; char *logfile = NULL; int cfd = 0; int forking = 0; struct msgtemplate msg; while (!forking) { c = getopt(argc, argv, "m:vr:"); if (c < 0) break; switch (c) { case 'w': logfile = strdup(optarg); printf("write to file %s\n", logfile); write_file = 1; break; case 'r': rcvbufsz = atoi(optarg); printf("receive buf size %d\n", rcvbufsz); if (rcvbufsz < 0) err(1, "Invalid rcv buf size\n"); break; case 'm': strncpy(cpumask, optarg, sizeof(cpumask)); cpumask[sizeof(cpumask) - 1] = '\0'; maskset = 1; break; case 'v': printf("debug on\n"); dbg = 1; break; default: usage(); exit(-1); } } if (!maskset) { maskset = 1; strncpy(cpumask, "1", sizeof(cpumask)); cpumask[sizeof(cpumask) - 1] = '\0'; } printf("cpumask %s maskset %d\n", cpumask, maskset); if (write_file) { fd = open(logfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("Cannot open output file\n"); exit(1); } } nl_sd = create_nl_socket(NETLINK_GENERIC); if (nl_sd < 0) err(1, "error creating Netlink socket\n"); mypid = getpid(); id = get_family_id(nl_sd); if (!id) { fprintf(stderr, "Error getting family id, errno %d\n", errno); goto err; } PRINTF("family id %d\n", id); if (maskset) { rc = send_cmd(nl_sd, id, mypid, TASKSTATS_CMD_GET, TASKSTATS_CMD_ATTR_REGISTER_CPUMASK, &cpumask, strlen(cpumask) + 1); PRINTF("Sent register cpumask, retval %d\n", rc); if (rc < 0) { fprintf(stderr, "error sending register cpumask\n"); goto err; } } do { rep_len = recv(nl_sd, &msg, sizeof(msg), 0); PRINTF("received %d bytes\n", rep_len); if (rep_len < 0) { fprintf(stderr, "nonfatal reply error: errno %d\n", errno); continue; } if (msg.n.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.n), rep_len)) { struct nlmsgerr *err = NLMSG_DATA(&msg); fprintf(stderr, "fatal reply error, errno %d\n", err->error); goto done; } PRINTF("nlmsghdr size=%zu, nlmsg_len=%d, rep_len=%d\n", sizeof(struct nlmsghdr), msg.n.nlmsg_len, rep_len); rep_len = GENLMSG_PAYLOAD(&msg.n); na = (struct nlattr *) GENLMSG_DATA(&msg); len = 0; while (len < rep_len) { len += NLA_ALIGN(na->nla_len); int mother = na->nla_type; PRINTF("mother=%i\n", mother); switch (na->nla_type) { case TASKSTATS_TYPE_AGGR_PID: case TASKSTATS_TYPE_AGGR_TGID: /* For nested attributes, na follows */ handle_aggr(mother, na, fd); break; default: fprintf(stderr, "Unexpected nla_type %d\n", na->nla_type); case TASKSTATS_TYPE_NULL: break; } na = (struct nlattr *) (GENLMSG_DATA(&msg) + len); } } while (1); done: if (maskset) { rc = send_cmd(nl_sd, id, mypid, TASKSTATS_CMD_GET, TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK, &cpumask, strlen(cpumask) + 1); printf("Sent deregister mask, retval %d\n", rc); if (rc < 0) err(rc, "error sending deregister cpumask\n"); } err: close(nl_sd); if (fd) close(fd); if (cfd) close(cfd); return 0; }