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/handlers.h> 29#include <netlink/msg.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <sys/cdefs.h> 33#include <time.h> 34#include <unistd.h> 35 36#include <linux/taskstats.h> 37 38struct TaskStatistics { 39 int pid; 40 int tgid; 41 struct taskstats stats; 42}; 43 44int 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 65int print_receive_error(struct sockaddr_nl* address __unused, 66 struct nlmsgerr* error, void* arg __unused) { 67 fprintf(stderr, "Netlink receive error: %s\n", strerror(-error->error)); 68 return NL_STOP; 69} 70 71int 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 85int 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 106void 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 125int 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 145int 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 168double average_ms(unsigned long long total, unsigned long long count) { 169 if (!count) { 170 return 0; 171 } 172 return ((double)total) / count / 1e6; 173} 174 175unsigned long long average_ns(unsigned long long total, 176 unsigned long long count) { 177 if (!count) { 178 return 0; 179 } 180 return total / count; 181} 182 183void 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 289void 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 306int 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 374error: 375 if (netlink_socket) { 376 nl_socket_free(netlink_socket); 377 } 378 return EXIT_FAILURE; 379} 380