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> |
| 28 | #include <netlink/handlers.h> |
| 29 | #include <netlink/msg.h> |
| 30 | #include <stdio.h> |
| 31 | #include <stdlib.h> |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 32 | #include <sys/cdefs.h> |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 33 | #include <time.h> |
| 34 | #include <unistd.h> |
| 35 | |
| 36 | #include <linux/taskstats.h> |
| 37 | |
| 38 | struct TaskStatistics { |
| 39 | int pid; |
| 40 | int tgid; |
| 41 | struct taskstats stats; |
| 42 | }; |
| 43 | |
| 44 | int send_command(struct nl_sock* netlink_socket, uint16_t nlmsg_type, |
| 45 | uint32_t nlmsg_pid, uint8_t genl_cmd, uint16_t nla_type, |
| 46 | void* nla_data, int nla_len) { |
| 47 | struct nl_msg* message = nlmsg_alloc(); |
| 48 | int seq = 0; |
| 49 | int version = 1; |
| 50 | int header_length = 0; |
| 51 | int flags = NLM_F_REQUEST; |
| 52 | genlmsg_put(message, nlmsg_pid, seq, nlmsg_type, header_length, flags, |
| 53 | genl_cmd, version); |
| 54 | nla_put(message, nla_type, nla_len, nla_data); |
| 55 | |
| 56 | /* Override the header flags since we don't want NLM_F_ACK. */ |
| 57 | struct nlmsghdr* header = nlmsg_hdr(message); |
| 58 | header->nlmsg_flags = flags; |
| 59 | |
| 60 | int result = nl_send(netlink_socket, message); |
| 61 | nlmsg_free(message); |
| 62 | return result; |
| 63 | } |
| 64 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 65 | int print_receive_error(struct sockaddr_nl* address __unused, |
| 66 | struct nlmsgerr* error, void* arg __unused) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 67 | fprintf(stderr, "Netlink receive error: %s\n", strerror(-error->error)); |
| 68 | return NL_STOP; |
| 69 | } |
| 70 | |
| 71 | int parse_family_id(struct nl_msg* msg, void* 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 | do { |
| 77 | if (attr->nla_type == CTRL_ATTR_FAMILY_ID) { |
| 78 | *((int*)arg) = nla_get_u16(attr); |
| 79 | return NL_STOP; |
| 80 | } |
| 81 | } while ((attr = nla_next(attr, &remaining))); |
| 82 | return NL_OK; |
| 83 | } |
| 84 | |
| 85 | int get_family_id(struct nl_sock* netlink_socket, const char* name) { |
| 86 | if (send_command(netlink_socket, GENL_ID_CTRL, getpid(), |
| 87 | CTRL_CMD_GETFAMILY, |
| 88 | CTRL_ATTR_FAMILY_NAME, |
| 89 | (void*)name, strlen(name) + 1) < 0) { |
| 90 | return 0; |
| 91 | } |
| 92 | |
| 93 | int family_id = 0; |
| 94 | struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_VALID)); |
| 95 | nl_cb_set(callbacks, NL_CB_VALID, NL_CB_DEFAULT, &parse_family_id, |
| 96 | &family_id); |
| 97 | nl_cb_err(callbacks, NL_CB_DEFAULT, &print_receive_error, NULL); |
| 98 | |
| 99 | if (nl_recvmsgs(netlink_socket, callbacks) < 0) { |
| 100 | return 0; |
| 101 | } |
| 102 | nl_cb_put(callbacks); |
| 103 | return family_id; |
| 104 | } |
| 105 | |
| 106 | void parse_aggregate_task_stats(struct nlattr* attr, int attr_size, |
| 107 | struct TaskStatistics* stats) { |
| 108 | do { |
| 109 | switch (attr->nla_type) { |
| 110 | case TASKSTATS_TYPE_PID: |
| 111 | stats->pid = nla_get_u32(attr); |
| 112 | break; |
| 113 | case TASKSTATS_TYPE_TGID: |
| 114 | stats->tgid = nla_get_u32(attr); |
| 115 | break; |
| 116 | case TASKSTATS_TYPE_STATS: |
| 117 | nla_memcpy(&stats->stats, attr, sizeof(stats->stats)); |
| 118 | break; |
| 119 | default: |
| 120 | break; |
| 121 | } |
| 122 | } while ((attr = nla_next(attr, &attr_size))); |
| 123 | } |
| 124 | |
| 125 | int parse_task_stats(struct nl_msg* msg, void* arg) { |
| 126 | struct TaskStatistics* stats = (struct TaskStatistics*)arg; |
| 127 | struct genlmsghdr* gnlh = (struct genlmsghdr*)nlmsg_data(nlmsg_hdr(msg)); |
| 128 | struct nlattr* attr = genlmsg_attrdata(gnlh, 0); |
| 129 | int remaining = genlmsg_attrlen(gnlh, 0); |
| 130 | |
| 131 | do { |
| 132 | switch (attr->nla_type) { |
| 133 | case TASKSTATS_TYPE_AGGR_PID: |
| 134 | case TASKSTATS_TYPE_AGGR_TGID: |
| 135 | parse_aggregate_task_stats(nla_data(attr), nla_len(attr), |
| 136 | stats); |
| 137 | break; |
| 138 | default: |
| 139 | break; |
| 140 | } |
| 141 | } while ((attr = nla_next(attr, &remaining))); |
| 142 | return NL_STOP; |
| 143 | } |
| 144 | |
| 145 | int query_task_stats(struct nl_sock* netlink_socket, int family_id, |
| 146 | int command_type, int parameter, |
| 147 | struct TaskStatistics* stats) { |
| 148 | memset(stats, 0, sizeof(*stats)); |
| 149 | int result = send_command(netlink_socket, family_id, getpid(), |
| 150 | TASKSTATS_CMD_GET, command_type, ¶meter, |
| 151 | sizeof(parameter)); |
| 152 | if (result < 0) { |
| 153 | return result; |
| 154 | } |
| 155 | |
| 156 | struct nl_cb* callbacks = nl_cb_get(nl_cb_alloc(NL_CB_VALID)); |
| 157 | nl_cb_set(callbacks, NL_CB_VALID, NL_CB_DEFAULT, &parse_task_stats, stats); |
| 158 | nl_cb_err(callbacks, NL_CB_DEFAULT, &print_receive_error, &family_id); |
| 159 | |
| 160 | result = nl_recvmsgs(netlink_socket, callbacks); |
| 161 | if (result < 0) { |
| 162 | return result; |
| 163 | } |
| 164 | nl_cb_put(callbacks); |
| 165 | return stats->pid || stats->tgid; |
| 166 | } |
| 167 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 168 | double average_ms(unsigned long long total, unsigned long long count) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 169 | if (!count) { |
| 170 | return 0; |
| 171 | } |
| 172 | return ((double)total) / count / 1e6; |
| 173 | } |
| 174 | |
Colin Cross | e7e7cc7 | 2014-03-20 11:15:26 -0700 | [diff] [blame] | 175 | unsigned long long average_ns(unsigned long long total, |
| 176 | unsigned long long count) { |
Sami Kyostila | 178e74d | 2013-09-20 17:28:26 +0100 | [diff] [blame] | 177 | if (!count) { |
| 178 | return 0; |
| 179 | } |
| 180 | return total / count; |
| 181 | } |
| 182 | |
| 183 | void print_task_stats(const struct TaskStatistics* stats, |
| 184 | int human_readable) { |
| 185 | const struct taskstats* s = &stats->stats; |
| 186 | printf("Basic task statistics\n"); |
| 187 | printf("---------------------\n"); |
| 188 | printf("%-25s%d\n", "Stats version:", s->version); |
| 189 | printf("%-25s%d\n", "Exit code:", s->ac_exitcode); |
| 190 | printf("%-25s0x%x\n", "Flags:", s->ac_flag); |
| 191 | printf("%-25s%d\n", "Nice value:", s->ac_nice); |
| 192 | printf("%-25s%s\n", "Command name:", s->ac_comm); |
| 193 | printf("%-25s%d\n", "Scheduling discipline:", s->ac_sched); |
| 194 | printf("%-25s%d\n", "UID:", s->ac_uid); |
| 195 | printf("%-25s%d\n", "GID:", s->ac_gid); |
| 196 | printf("%-25s%d\n", "PID:", s->ac_pid); |
| 197 | printf("%-25s%d\n", "PPID:", s->ac_ppid); |
| 198 | |
| 199 | if (human_readable) { |
| 200 | time_t begin_time = s->ac_btime; |
| 201 | printf("%-25s%s", "Begin time:", ctime(&begin_time)); |
| 202 | } else { |
| 203 | printf("%-25s%d sec\n", "Begin time:", s->ac_btime); |
| 204 | } |
| 205 | printf("%-25s%llu usec\n", "Elapsed time:", s->ac_etime); |
| 206 | printf("%-25s%llu usec\n", "User CPU time:", s->ac_utime); |
| 207 | printf("%-25s%llu\n", "Minor page faults:", s->ac_minflt); |
| 208 | printf("%-25s%llu\n", "Major page faults:", s->ac_majflt); |
| 209 | printf("%-25s%llu usec\n", "Scaled user time:", s->ac_utimescaled); |
| 210 | printf("%-25s%llu usec\n", "Scaled system time:", s->ac_stimescaled); |
| 211 | |
| 212 | printf("\nDelay accounting\n"); |
| 213 | printf("----------------\n"); |
| 214 | printf(" %15s%15s%15s%15s%15s%15s\n", |
| 215 | "Count", |
| 216 | human_readable ? "Delay (ms)" : "Delay (ns)", |
| 217 | "Average delay", |
| 218 | "Real delay", |
| 219 | "Scaled real", |
| 220 | "Virtual delay"); |
| 221 | |
| 222 | if (!human_readable) { |
| 223 | printf("CPU %15llu%15llu%15llu%15llu%15llu%15llu\n", |
| 224 | s->cpu_count, |
| 225 | s->cpu_delay_total, |
| 226 | average_ns(s->cpu_delay_total, s->cpu_count), |
| 227 | s->cpu_run_real_total, |
| 228 | s->cpu_scaled_run_real_total, |
| 229 | s->cpu_run_virtual_total); |
| 230 | printf("IO %15llu%15llu%15llu\n", |
| 231 | s->blkio_count, |
| 232 | s->blkio_delay_total, |
| 233 | average_ns(s->blkio_delay_total, s->blkio_count)); |
| 234 | printf("Swap %15llu%15llu%15llu\n", |
| 235 | s->swapin_count, |
| 236 | s->swapin_delay_total, |
| 237 | average_ns(s->swapin_delay_total, s->swapin_count)); |
| 238 | printf("Reclaim%15llu%15llu%15llu\n", |
| 239 | s->freepages_count, |
| 240 | s->freepages_delay_total, |
| 241 | average_ns(s->freepages_delay_total, s->freepages_count)); |
| 242 | } else { |
| 243 | const double ms_per_ns = 1e6; |
| 244 | printf("CPU %15llu%15.3f%15.3f%15.3f%15.3f%15.3f\n", |
| 245 | s->cpu_count, |
| 246 | s->cpu_delay_total / ms_per_ns, |
| 247 | average_ms(s->cpu_delay_total, s->cpu_count), |
| 248 | s->cpu_run_real_total / ms_per_ns, |
| 249 | s->cpu_scaled_run_real_total / ms_per_ns, |
| 250 | s->cpu_run_virtual_total / ms_per_ns); |
| 251 | printf("IO %15llu%15.3f%15.3f\n", |
| 252 | s->blkio_count, |
| 253 | s->blkio_delay_total / ms_per_ns, |
| 254 | average_ms(s->blkio_delay_total, s->blkio_count)); |
| 255 | printf("Swap %15llu%15.3f%15.3f\n", |
| 256 | s->swapin_count, |
| 257 | s->swapin_delay_total / ms_per_ns, |
| 258 | average_ms(s->swapin_delay_total, s->swapin_count)); |
| 259 | printf("Reclaim%15llu%15.3f%15.3f\n", |
| 260 | s->freepages_count, |
| 261 | s->freepages_delay_total / ms_per_ns, |
| 262 | average_ms(s->freepages_delay_total, s->freepages_count)); |
| 263 | } |
| 264 | |
| 265 | printf("\nExtended accounting fields\n"); |
| 266 | printf("--------------------------\n"); |
| 267 | if (human_readable && s->ac_stime) { |
| 268 | printf("%-25s%.3f MB\n", "Average RSS usage:", |
| 269 | (double)s->coremem / s->ac_stime); |
| 270 | printf("%-25s%.3f MB\n", "Average VM usage:", |
| 271 | (double)s->virtmem / s->ac_stime); |
| 272 | } else { |
| 273 | printf("%-25s%llu MB\n", "Accumulated RSS usage:", s->coremem); |
| 274 | printf("%-25s%llu MB\n", "Accumulated VM usage:", s->virtmem); |
| 275 | } |
| 276 | printf("%-25s%llu KB\n", "RSS high water mark:", s->hiwater_rss); |
| 277 | printf("%-25s%llu KB\n", "VM high water mark:", s->hiwater_vm); |
| 278 | printf("%-25s%llu\n", "IO bytes read:", s->read_char); |
| 279 | printf("%-25s%llu\n", "IO bytes written:", s->write_char); |
| 280 | printf("%-25s%llu\n", "IO read syscalls:", s->read_syscalls); |
| 281 | printf("%-25s%llu\n", "IO write syscalls:", s->write_syscalls); |
| 282 | |
| 283 | printf("\nPer-task/thread statistics\n"); |
| 284 | printf("--------------------------\n"); |
| 285 | printf("%-25s%llu\n", "Voluntary switches:", s->nvcsw); |
| 286 | printf("%-25s%llu\n", "Involuntary switches:", s->nivcsw); |
| 287 | } |
| 288 | |
| 289 | void print_usage() { |
| 290 | printf("Linux task stats reporting tool\n" |
| 291 | "\n" |
| 292 | "Usage: taskstats [options]\n" |
| 293 | "\n" |
| 294 | "Options:\n" |
| 295 | " --help This text\n" |
| 296 | " --pid PID Print stats for the process id PID\n" |
| 297 | " --tgid TGID Print stats for the thread group id TGID\n" |
| 298 | " --raw Print raw numbers instead of human readable units\n" |
| 299 | "\n" |
| 300 | "Either PID or TGID must be specified. For more documentation about " |
| 301 | "the reported fields, see\n" |
| 302 | "https://www.kernel.org/doc/Documentation/accounting/" |
| 303 | "taskstats-struct.txt\n"); |
| 304 | } |
| 305 | |
| 306 | int main(int argc, char** argv) { |
| 307 | int command_type = 0; |
| 308 | int pid = 0; |
| 309 | int human_readable = 1; |
| 310 | |
| 311 | const struct option long_options[] = { |
| 312 | {"help", no_argument, 0, 0}, |
| 313 | {"pid", required_argument, 0, 0}, |
| 314 | {"tgid", required_argument, 0, 0}, |
| 315 | {"raw", no_argument, 0, 0}, |
| 316 | {0, 0, 0, 0} |
| 317 | }; |
| 318 | |
| 319 | while (1) { |
| 320 | int option_index; |
| 321 | int option_char = getopt_long_only(argc, argv, "", long_options, |
| 322 | &option_index); |
| 323 | if (option_char == -1) { |
| 324 | break; |
| 325 | } |
| 326 | switch (option_index) { |
| 327 | case 0: |
| 328 | print_usage(); |
| 329 | return EXIT_SUCCESS; |
| 330 | case 1: |
| 331 | command_type = TASKSTATS_CMD_ATTR_PID; |
| 332 | pid = atoi(optarg); |
| 333 | break; |
| 334 | case 2: |
| 335 | command_type = TASKSTATS_CMD_ATTR_TGID; |
| 336 | pid = atoi(optarg); |
| 337 | break; |
| 338 | case 3: |
| 339 | human_readable = 0; |
| 340 | break; |
| 341 | default: |
| 342 | break; |
| 343 | }; |
| 344 | } |
| 345 | |
| 346 | if (!pid) { |
| 347 | printf("Either PID or TGID must be specified\n"); |
| 348 | return EXIT_FAILURE; |
| 349 | } |
| 350 | |
| 351 | struct nl_sock* netlink_socket = nl_socket_alloc(); |
| 352 | if (!netlink_socket || genl_connect(netlink_socket) < 0) { |
| 353 | perror("Unable to open netlink socket (are you root?)"); |
| 354 | goto error; |
| 355 | } |
| 356 | |
| 357 | int family_id = get_family_id(netlink_socket, TASKSTATS_GENL_NAME); |
| 358 | if (!family_id) { |
| 359 | perror("Unable to determine taskstats family id " |
| 360 | "(does your kernel support taskstats?)"); |
| 361 | goto error; |
| 362 | } |
| 363 | struct TaskStatistics stats; |
| 364 | if (query_task_stats(netlink_socket, family_id, command_type, pid, |
| 365 | &stats) < 0) { |
| 366 | perror("Failed to query taskstats"); |
| 367 | goto error; |
| 368 | } |
| 369 | print_task_stats(&stats, human_readable); |
| 370 | |
| 371 | nl_socket_free(netlink_socket); |
| 372 | return EXIT_SUCCESS; |
| 373 | |
| 374 | error: |
| 375 | if (netlink_socket) { |
| 376 | nl_socket_free(netlink_socket); |
| 377 | } |
| 378 | return EXIT_FAILURE; |
| 379 | } |