Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 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> |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 26 | #include <netlink/attr.h> |
| 27 | #include <netlink/genl/genl.h> |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 28 | #include <netlink/genl/ctrl.h> |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 29 | #include <netlink/handlers.h> |
| 30 | #include <netlink/msg.h> |
| 31 | #include <stdio.h> |
| 32 | #include <stdlib.h> |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 33 | #include <sys/cdefs.h> |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 34 | #include <time.h> |
| 35 | #include <unistd.h> |
| 36 | |
| 37 | #include <linux/taskstats.h> |
| 38 | |
| 39 | struct TaskStatistics { |
| 40 | int pid; |
| 41 | int tgid; |
| 42 | struct taskstats stats; |
| 43 | }; |
| 44 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 45 | int print_receive_error(struct sockaddr_nl* address __unused, |
| 46 | struct nlmsgerr* error, void* arg __unused) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 47 | fprintf(stderr, "Netlink receive error: %s\n", strerror(-error->error)); |
| 48 | return NL_STOP; |
| 49 | } |
| 50 | |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 51 | void parse_aggregate_task_stats(struct nlattr* attr, int attr_size, |
| 52 | struct TaskStatistics* stats) { |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 53 | nla_for_each_attr(attr, attr, attr_size, attr_size) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 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 | } |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 67 | } |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | int 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 | |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 76 | nla_for_each_attr(attr, attr, remaining, remaining) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 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 | } |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 86 | } |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 87 | return NL_STOP; |
| 88 | } |
| 89 | |
| 90 | int 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)); |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 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); |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 102 | if (result < 0) { |
| 103 | return result; |
| 104 | } |
| 105 | |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 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); |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 109 | |
| 110 | result = nl_recvmsgs(netlink_socket, callbacks); |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 111 | nl_cb_put(callbacks); |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 112 | if (result < 0) { |
| 113 | return result; |
| 114 | } |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 115 | return stats->pid || stats->tgid; |
| 116 | } |
| 117 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 118 | double average_ms(unsigned long long total, unsigned long long count) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 119 | if (!count) { |
| 120 | return 0; |
| 121 | } |
| 122 | return ((double)total) / count / 1e6; |
| 123 | } |
| 124 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 125 | unsigned long long average_ns(unsigned long long total, |
| 126 | unsigned long long count) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 127 | if (!count) { |
| 128 | return 0; |
| 129 | } |
| 130 | return total / count; |
| 131 | } |
| 132 | |
| 133 | void 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); |
Peter Enderborg | 0e0b85d | 2019-07-30 09:32:39 +0200 | [diff] [blame] | 237 | |
| 238 | #if TASKSTATS_VERSION > 8 |
| 239 | if (s->version > 8) { |
| 240 | printf("%-25s%llu\n", "Thrashing count:", s->thrashing_count); |
| 241 | printf("%-25s%llu\n", "Thrashing delay total:", s->thrashing_delay_total); |
| 242 | } |
| 243 | #endif |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 244 | } |
| 245 | |
| 246 | void print_usage() { |
| 247 | printf("Linux task stats reporting tool\n" |
| 248 | "\n" |
| 249 | "Usage: taskstats [options]\n" |
| 250 | "\n" |
| 251 | "Options:\n" |
| 252 | " --help This text\n" |
| 253 | " --pid PID Print stats for the process id PID\n" |
| 254 | " --tgid TGID Print stats for the thread group id TGID\n" |
| 255 | " --raw Print raw numbers instead of human readable units\n" |
| 256 | "\n" |
| 257 | "Either PID or TGID must be specified. For more documentation about " |
| 258 | "the reported fields, see\n" |
| 259 | "https://www.kernel.org/doc/Documentation/accounting/" |
| 260 | "taskstats-struct.txt\n"); |
| 261 | } |
| 262 | |
| 263 | int main(int argc, char** argv) { |
| 264 | int command_type = 0; |
| 265 | int pid = 0; |
| 266 | int human_readable = 1; |
| 267 | |
| 268 | const struct option long_options[] = { |
| 269 | {"help", no_argument, 0, 0}, |
| 270 | {"pid", required_argument, 0, 0}, |
| 271 | {"tgid", required_argument, 0, 0}, |
| 272 | {"raw", no_argument, 0, 0}, |
| 273 | {0, 0, 0, 0} |
| 274 | }; |
| 275 | |
| 276 | while (1) { |
| 277 | int option_index; |
| 278 | int option_char = getopt_long_only(argc, argv, "", long_options, |
| 279 | &option_index); |
| 280 | if (option_char == -1) { |
| 281 | break; |
| 282 | } |
| 283 | switch (option_index) { |
| 284 | case 0: |
| 285 | print_usage(); |
| 286 | return EXIT_SUCCESS; |
| 287 | case 1: |
| 288 | command_type = TASKSTATS_CMD_ATTR_PID; |
| 289 | pid = atoi(optarg); |
| 290 | break; |
| 291 | case 2: |
| 292 | command_type = TASKSTATS_CMD_ATTR_TGID; |
| 293 | pid = atoi(optarg); |
| 294 | break; |
| 295 | case 3: |
| 296 | human_readable = 0; |
| 297 | break; |
| 298 | default: |
| 299 | break; |
| 300 | }; |
| 301 | } |
| 302 | |
| 303 | if (!pid) { |
| 304 | printf("Either PID or TGID must be specified\n"); |
| 305 | return EXIT_FAILURE; |
| 306 | } |
| 307 | |
| 308 | struct nl_sock* netlink_socket = nl_socket_alloc(); |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 309 | if (!netlink_socket) { |
| 310 | fprintf(stderr, "Unable to allocate netlink socket\n"); |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 311 | goto error; |
| 312 | } |
| 313 | |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 314 | int ret = genl_connect(netlink_socket); |
| 315 | if (ret < 0) { |
| 316 | nl_perror(ret, "Unable to open netlink socket (are you root?)"); |
| 317 | goto error; |
| 318 | } |
| 319 | |
| 320 | int family_id = genl_ctrl_resolve(netlink_socket, TASKSTATS_GENL_NAME); |
| 321 | if (family_id < 0) { |
| 322 | nl_perror(family_id, "Unable to determine taskstats family id " |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 323 | "(does your kernel support taskstats?)"); |
| 324 | goto error; |
| 325 | } |
| 326 | struct TaskStatistics stats; |
Colin Cross | 1ed4c8d | 2015-09-01 23:24:13 -0700 | [diff] [blame] | 327 | ret = query_task_stats(netlink_socket, family_id, command_type, pid, &stats); |
| 328 | if (ret < 0) { |
| 329 | nl_perror(ret, "Failed to query taskstats"); |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 330 | goto error; |
| 331 | } |
| 332 | print_task_stats(&stats, human_readable); |
| 333 | |
| 334 | nl_socket_free(netlink_socket); |
| 335 | return EXIT_SUCCESS; |
| 336 | |
| 337 | error: |
| 338 | if (netlink_socket) { |
| 339 | nl_socket_free(netlink_socket); |
| 340 | } |
| 341 | return EXIT_FAILURE; |
| 342 | } |