1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Linux task stats reporting tool. Queries and prints out the kernel's
19 * taskstats structure for a given process or thread group id. See
20 * https://www.kernel.org/doc/Documentation/accounting/ for more information
21 * about the reported fields.
22 */
23
24#include <errno.h>
25#include <getopt.h>
26#include <netlink/attr.h>
27#include <netlink/genl/genl.h>
28#include <netlink/genl/ctrl.h>
29#include <netlink/handlers.h>
30#include <netlink/msg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <sys/cdefs.h>
34#include <time.h>
35#include <unistd.h>
36
37#include <linux/taskstats.h>
38
39struct TaskStatistics {
40    int pid;
41    int tgid;
42    struct taskstats stats;
43};
44
45int print_receive_error(struct sockaddr_nl* address __unused,
46                        struct nlmsgerr* error, void* arg __unused) {
47    fprintf(stderr, "Netlink receive error: %s\n", strerror(-error->error));
48    return NL_STOP;
49}
50
51void parse_aggregate_task_stats(struct nlattr* attr, int attr_size,
52                                struct TaskStatistics* stats) {
53    nla_for_each_attr(attr, attr, attr_size, attr_size) {
54        switch (attr->nla_type) {
55            case TASKSTATS_TYPE_PID:
56                stats->pid = nla_get_u32(attr);
57                break;
58            case TASKSTATS_TYPE_TGID:
59                stats->tgid = nla_get_u32(attr);
60                break;
61            case TASKSTATS_TYPE_STATS:
62                nla_memcpy(&stats->stats, attr, sizeof(stats->stats));
63                break;
64            default:
65                break;
66        }
67    }
68}
69
70int parse_task_stats(struct nl_msg* msg, void* arg) {
71    struct TaskStatistics* stats = (struct TaskStatistics*)arg;
72    struct genlmsghdr* gnlh = (struct genlmsghdr*)nlmsg_data(nlmsg_hdr(msg));
73    struct nlattr* attr = genlmsg_attrdata(gnlh, 0);
74    int remaining = genlmsg_attrlen(gnlh, 0);
75
76    nla_for_each_attr(attr, attr, remaining, remaining) {
77        switch (attr->nla_type) {
78            case TASKSTATS_TYPE_AGGR_PID:
79            case TASKSTATS_TYPE_AGGR_TGID:
80                parse_aggregate_task_stats(nla_data(attr), nla_len(attr),
81                                           stats);
82                break;
83            default:
84                break;
85        }
86    }
87    return NL_STOP;
88}
89
90int query_task_stats(struct nl_sock* netlink_socket, int family_id,
91                     int command_type, int parameter,
92                     struct TaskStatistics* stats) {
93    memset(stats, 0, sizeof(*stats));
94
95    struct nl_msg* message = nlmsg_alloc();
96    genlmsg_put(message, NL_AUTO_PID, NL_AUTO_SEQ, family_id, 0, 0,
97		TASKSTATS_CMD_GET, TASKSTATS_VERSION);
98    nla_put_u32(message, command_type, parameter);
99
100    int result = nl_send_auto_complete(netlink_socket, message);
101    nlmsg_free(message);
102    if (result < 0) {
103        return result;
104    }
105
106    struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_CUSTOM));
107    nl_cb_set(callbacks, NL_CB_VALID, NL_CB_CUSTOM, &parse_task_stats, stats);
108    nl_cb_err(callbacks, NL_CB_CUSTOM, &print_receive_error, &family_id);
109
110    result = nl_recvmsgs(netlink_socket, callbacks);
111    nl_cb_put(callbacks);
112    if (result < 0) {
113        return result;
114    }
115    return stats->pid || stats->tgid;
116}
117
118double average_ms(unsigned long long total, unsigned long long count) {
119    if (!count) {
120        return 0;
121    }
122    return ((double)total) / count / 1e6;
123}
124
125unsigned long long average_ns(unsigned long long total,
126                              unsigned long long count) {
127    if (!count) {
128        return 0;
129    }
130    return total / count;
131}
132
133void print_task_stats(const struct TaskStatistics* stats,
134                      int human_readable) {
135    const struct taskstats* s = &stats->stats;
136    printf("Basic task statistics\n");
137    printf("---------------------\n");
138    printf("%-25s%d\n", "Stats version:", s->version);
139    printf("%-25s%d\n", "Exit code:", s->ac_exitcode);
140    printf("%-25s0x%x\n", "Flags:", s->ac_flag);
141    printf("%-25s%d\n", "Nice value:", s->ac_nice);
142    printf("%-25s%s\n", "Command name:", s->ac_comm);
143    printf("%-25s%d\n", "Scheduling discipline:", s->ac_sched);
144    printf("%-25s%d\n", "UID:", s->ac_uid);
145    printf("%-25s%d\n", "GID:", s->ac_gid);
146    printf("%-25s%d\n", "PID:", s->ac_pid);
147    printf("%-25s%d\n", "PPID:", s->ac_ppid);
148
149    if (human_readable) {
150        time_t begin_time = s->ac_btime;
151        printf("%-25s%s", "Begin time:", ctime(&begin_time));
152    } else {
153        printf("%-25s%d sec\n", "Begin time:", s->ac_btime);
154    }
155    printf("%-25s%llu usec\n", "Elapsed time:", s->ac_etime);
156    printf("%-25s%llu usec\n", "User CPU time:", s->ac_utime);
157    printf("%-25s%llu\n", "Minor page faults:", s->ac_minflt);
158    printf("%-25s%llu\n", "Major page faults:", s->ac_majflt);
159    printf("%-25s%llu usec\n", "Scaled user time:", s->ac_utimescaled);
160    printf("%-25s%llu usec\n", "Scaled system time:", s->ac_stimescaled);
161
162    printf("\nDelay accounting\n");
163    printf("----------------\n");
164    printf("       %15s%15s%15s%15s%15s%15s\n",
165           "Count",
166           human_readable ? "Delay (ms)" : "Delay (ns)",
167           "Average delay",
168           "Real delay",
169           "Scaled real",
170           "Virtual delay");
171
172    if (!human_readable) {
173        printf("CPU    %15llu%15llu%15llu%15llu%15llu%15llu\n",
174               s->cpu_count,
175               s->cpu_delay_total,
176               average_ns(s->cpu_delay_total, s->cpu_count),
177               s->cpu_run_real_total,
178               s->cpu_scaled_run_real_total,
179               s->cpu_run_virtual_total);
180        printf("IO     %15llu%15llu%15llu\n",
181               s->blkio_count,
182               s->blkio_delay_total,
183               average_ns(s->blkio_delay_total, s->blkio_count));
184        printf("Swap   %15llu%15llu%15llu\n",
185               s->swapin_count,
186               s->swapin_delay_total,
187               average_ns(s->swapin_delay_total, s->swapin_count));
188        printf("Reclaim%15llu%15llu%15llu\n",
189               s->freepages_count,
190               s->freepages_delay_total,
191               average_ns(s->freepages_delay_total, s->freepages_count));
192    } else {
193        const double ms_per_ns = 1e6;
194        printf("CPU    %15llu%15.3f%15.3f%15.3f%15.3f%15.3f\n",
195               s->cpu_count,
196               s->cpu_delay_total / ms_per_ns,
197               average_ms(s->cpu_delay_total, s->cpu_count),
198               s->cpu_run_real_total / ms_per_ns,
199               s->cpu_scaled_run_real_total / ms_per_ns,
200               s->cpu_run_virtual_total / ms_per_ns);
201        printf("IO     %15llu%15.3f%15.3f\n",
202               s->blkio_count,
203               s->blkio_delay_total / ms_per_ns,
204               average_ms(s->blkio_delay_total, s->blkio_count));
205        printf("Swap   %15llu%15.3f%15.3f\n",
206               s->swapin_count,
207               s->swapin_delay_total / ms_per_ns,
208               average_ms(s->swapin_delay_total, s->swapin_count));
209        printf("Reclaim%15llu%15.3f%15.3f\n",
210               s->freepages_count,
211               s->freepages_delay_total / ms_per_ns,
212               average_ms(s->freepages_delay_total, s->freepages_count));
213    }
214
215    printf("\nExtended accounting fields\n");
216    printf("--------------------------\n");
217    if (human_readable && s->ac_stime) {
218        printf("%-25s%.3f MB\n", "Average RSS usage:",
219               (double)s->coremem / s->ac_stime);
220        printf("%-25s%.3f MB\n", "Average VM usage:",
221               (double)s->virtmem / s->ac_stime);
222    } else {
223        printf("%-25s%llu MB\n", "Accumulated RSS usage:", s->coremem);
224        printf("%-25s%llu MB\n", "Accumulated VM usage:", s->virtmem);
225    }
226    printf("%-25s%llu KB\n", "RSS high water mark:", s->hiwater_rss);
227    printf("%-25s%llu KB\n", "VM high water mark:", s->hiwater_vm);
228    printf("%-25s%llu\n", "IO bytes read:", s->read_char);
229    printf("%-25s%llu\n", "IO bytes written:", s->write_char);
230    printf("%-25s%llu\n", "IO read syscalls:", s->read_syscalls);
231    printf("%-25s%llu\n", "IO write syscalls:", s->write_syscalls);
232
233    printf("\nPer-task/thread statistics\n");
234    printf("--------------------------\n");
235    printf("%-25s%llu\n", "Voluntary switches:", s->nvcsw);
236    printf("%-25s%llu\n", "Involuntary switches:", s->nivcsw);
237}
238
239void print_usage() {
240  printf("Linux task stats reporting tool\n"
241         "\n"
242         "Usage: taskstats [options]\n"
243         "\n"
244         "Options:\n"
245         "  --help        This text\n"
246         "  --pid PID     Print stats for the process id PID\n"
247         "  --tgid TGID   Print stats for the thread group id TGID\n"
248         "  --raw         Print raw numbers instead of human readable units\n"
249         "\n"
250         "Either PID or TGID must be specified. For more documentation about "
251         "the reported fields, see\n"
252         "https://www.kernel.org/doc/Documentation/accounting/"
253         "taskstats-struct.txt\n");
254}
255
256int main(int argc, char** argv) {
257    int command_type = 0;
258    int pid = 0;
259    int human_readable = 1;
260
261    const struct option long_options[] = {
262        {"help", no_argument, 0, 0},
263        {"pid", required_argument, 0, 0},
264        {"tgid", required_argument, 0, 0},
265        {"raw", no_argument, 0, 0},
266        {0, 0, 0, 0}
267    };
268
269    while (1) {
270        int option_index;
271        int option_char = getopt_long_only(argc, argv, "", long_options,
272                                           &option_index);
273        if (option_char == -1) {
274            break;
275        }
276        switch (option_index) {
277            case 0:
278                print_usage();
279                return EXIT_SUCCESS;
280            case 1:
281                command_type = TASKSTATS_CMD_ATTR_PID;
282                pid = atoi(optarg);
283                break;
284            case 2:
285                command_type = TASKSTATS_CMD_ATTR_TGID;
286                pid = atoi(optarg);
287                break;
288            case 3:
289                human_readable = 0;
290                break;
291            default:
292                break;
293        };
294    }
295
296    if (!pid) {
297        printf("Either PID or TGID must be specified\n");
298        return EXIT_FAILURE;
299    }
300
301    struct nl_sock* netlink_socket = nl_socket_alloc();
302    if (!netlink_socket) {
303        fprintf(stderr, "Unable to allocate netlink socket\n");
304        goto error;
305    }
306
307    int ret = genl_connect(netlink_socket);
308    if (ret < 0) {
309        nl_perror(ret, "Unable to open netlink socket (are you root?)");
310        goto error;
311    }
312
313    int family_id = genl_ctrl_resolve(netlink_socket, TASKSTATS_GENL_NAME);
314    if (family_id < 0) {
315        nl_perror(family_id, "Unable to determine taskstats family id "
316               "(does your kernel support taskstats?)");
317        goto error;
318    }
319    struct TaskStatistics stats;
320    ret = query_task_stats(netlink_socket, family_id, command_type, pid, &stats);
321    if (ret < 0) {
322        nl_perror(ret, "Failed to query taskstats");
323        goto error;
324    }
325    print_task_stats(&stats, human_readable);
326
327    nl_socket_free(netlink_socket);
328    return EXIT_SUCCESS;
329
330error:
331    if (netlink_socket) {
332        nl_socket_free(netlink_socket);
333    }
334    return EXIT_FAILURE;
335}
336