├── CMakeLists.txt ├── LICENSE ├── README ├── iopp.8 └── iopp.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.4) 2 | PROJECT(IOPP C) 3 | 4 | # 5 | # Files to build. 6 | # 7 | 8 | add_executable(iopp iopp.c) 9 | 10 | # 11 | # Set compiler flags. 12 | # 13 | 14 | SET_SOURCE_FILES_PROPERTIES(iopp.c COMPILE_FLAGS "-g -Wall") 15 | 16 | # 17 | # Install rules. 18 | # 19 | 20 | INSTALL(PROGRAMS iopp DESTINATION "/bin") 21 | INSTALL(FILES iopp.8 DESTINATION "/share/man/man8") 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005, William LeFebvre 2 | Copyright (c) 2007-2008, Mark Wong 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | * list of conditions and the following disclaimer. Redistributions in binary 11 | * form must reproduce the above copyright notice, this list of conditions and 12 | * the following disclaimer in the documentation and/or other materials provided 13 | * with the distribution. Neither the name of Mark Wong nor the names 14 | * of its contributors may be used to endorse or promote products derived from 15 | * this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | To build 'iopp': 2 | 3 | cmake CMakeLists.txt 4 | make 5 | 6 | ----- 7 | 8 | To install 'iopp': 9 | 10 | make install DESTDIR=/usr 11 | 12 | 'iopp' is installed into '/usr/bin'. 13 | 14 | ----- 15 | 16 | To uninstall 'iopp', run the following command from the source directory: 17 | 18 | xargs rm < install_manifest.txt 19 | 20 | The file 'install_manifest.txt' will be created after running 'make install'. 21 | -------------------------------------------------------------------------------- /iopp.8: -------------------------------------------------------------------------------- 1 | .\" Process this file with 2 | .\" groff -man -Tascii iopp.8 3 | .\" 4 | .TH IOPP 8 "OCTOBER 2008" IOPP "" 5 | .SH NAME 6 | iopp \- I/O statistics per process 7 | .SH SYNOPSIS 8 | .B iopp -h|--help 9 | 10 | .B iopp [\-ci] [\-k|\-m|\-u] [ 11 | .I delay 12 | .B [ 13 | .I count 14 | .B ] ] 15 | 16 | .B iopp [\-\-command] [\-\-idle] [\-\-kilobytes|\-\-megabytes|\-\-human\-readable] [ 17 | .I delay 18 | .B [ 19 | .I count 20 | .B ] ] 21 | .SH DESCRIPTION 22 | .B iopp 23 | reports I/O information from each process on the system. 24 | .SH OPTIONS 25 | .SS 26 | .B \-c, \-\-command 27 | .NF 28 | display full command line 29 | .FI 30 | .PP 31 | .SS 32 | .B \-h, \-\-help 33 | .NF 34 | display help 35 | .FI 36 | .PP 37 | .SS 38 | .B \-i, \-\-idle 39 | hide idle processes 40 | .FI 41 | .PP 42 | .SS 43 | .B \-k, \-\-kilobytes 44 | display data in kilobytes 45 | .FI 46 | .PP 47 | .SS 48 | .B \-m, \-\-megabytes 49 | .NF 50 | display data in megabytes 51 | .FI 52 | .PP 53 | .SS 54 | .B \-u, \-\-human-readable 55 | .NF 56 | display data in bytes, kilobytes, megabytes, or gigabytes 57 | .FI 58 | .PP 59 | .SS 60 | .I delay 61 | .NF 62 | delay between updates in seconds, if no delay is specified then only one 63 | report of 0's (zeros) is printed 64 | .FI 65 | .PP 66 | .SS 67 | .I count 68 | .NF 69 | the number of updates, if no count is specified and delay is defined then 70 | count defaults to infinity 71 | .FI 72 | .PP 73 | .SH FIELD DESCRIPTION 74 | .SS 75 | .B pid 76 | .NF 77 | The process id. 78 | .FI 79 | .PP 80 | .SS 81 | .B rchar 82 | .NF 83 | The number of bytes which this task has caused to be read from storage. 84 | .FI 85 | .PP 86 | .SS 87 | .B wchar 88 | .NF 89 | The number of bytes which this task has caused, or shall cause to be 90 | written to disk. 91 | .FI 92 | .PP 93 | .SS 94 | .B syscr 95 | .NF 96 | Count of the number of read I/O operations. 97 | .FI 98 | .PP 99 | .SS 100 | .B syscw 101 | .NF 102 | Count of the number of write I/O operations. 103 | .FI 104 | .PP 105 | .SS 106 | .B rbytes rkb rmb reads 107 | .NF 108 | Count of the number of bytes which this process really did cause to be 109 | fetched from the storage layer. 110 | .FI 111 | .PP 112 | .SS 113 | .B wbytes wkb wmb writes 114 | .NF 115 | Count of the number of bytes which this process really did cause to be 116 | sent to the storage layer. 117 | .FI 118 | .PP 119 | .SS 120 | .B cwbytes cwkb cwmb cwrites 121 | .NF 122 | The number of bytes which this process caused to not happen, by 123 | truncating pagecache. 124 | .FI 125 | .PP 126 | .SS 127 | .B command 128 | .NF 129 | Filename of the executable. 130 | .FI 131 | .PP 132 | .SH AUTHOR 133 | Mark Wong 134 | -------------------------------------------------------------------------------- /iopp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Mark Wong 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define PROC "/proc" 17 | 18 | #define GET_VALUE(v) \ 19 | p = strchr(p, ':'); \ 20 | ++p; \ 21 | ++p; \ 22 | q = strchr(p, '\n'); \ 23 | length = q - p; \ 24 | if (length >= BUFFERLEN) \ 25 | { \ 26 | printf("ERROR - value is larger than the buffer: %d\n", __LINE__); \ 27 | exit(1); \ 28 | } \ 29 | strncpy(value, p, length); \ 30 | value[length] = '\0'; \ 31 | v = atoll(value); 32 | 33 | #define BTOKB(b) b >> 10 34 | #define BTOMB(b) b >> 20 35 | 36 | #define BUFFERLEN 255 37 | #define COMMANDLEN 1024 38 | #define VALUELEN 63 39 | 40 | #define NUM_STRINGS 8 41 | 42 | struct io_node 43 | { 44 | int pid; 45 | long long rchar; 46 | long long wchar; 47 | long long syscr; 48 | long long syscw; 49 | long long read_bytes; 50 | long long write_bytes; 51 | long long cancelled_write_bytes; 52 | char command[COMMANDLEN + 1]; 53 | struct io_node *next; 54 | }; 55 | 56 | struct io_node *head = NULL; 57 | int command_flag = 0; 58 | int idle_flag = 0; 59 | int mb_flag = 0; 60 | int kb_flag = 0; 61 | int hr_flag = 0; 62 | 63 | /* Prototypes */ 64 | char *format_b(long long); 65 | struct io_node *get_ion(int); 66 | struct io_node *new_ion(char *); 67 | void upsert_data(struct io_node *); 68 | 69 | char * 70 | format_b(long long amt) 71 | { 72 | static char retarray[NUM_STRINGS][16]; 73 | static int index = 0; 74 | register char *ret; 75 | register char tag = 'B'; 76 | 77 | ret = retarray[index]; 78 | index = (index + 1) % NUM_STRINGS; 79 | 80 | if (amt >= 10000) { 81 | amt = (amt + 512) / 1024; 82 | tag = 'K'; 83 | if (amt >= 10000) { 84 | amt = (amt + 512) / 1024; 85 | tag = 'B'; 86 | if (amt >= 10000) { 87 | amt = (amt + 512) / 1024; 88 | tag = 'G'; 89 | } 90 | } 91 | } 92 | 93 | snprintf(ret, sizeof(retarray[index]) - 1, "%lld%c", amt, tag); 94 | 95 | return (ret); 96 | } 97 | 98 | int 99 | get_cmdline(struct io_node *ion) 100 | { 101 | int fd; 102 | int length; 103 | char filename[BUFFERLEN + 1]; 104 | char buffer[COMMANDLEN + 1]; 105 | char *p; 106 | char *q; 107 | 108 | 109 | length = snprintf(filename, BUFFERLEN, "%s/%d/cmdline", PROC, ion->pid); 110 | if (length == BUFFERLEN) 111 | printf("WARNING - filename length may be too big for buffer: %d\n", 112 | __LINE__); 113 | fd = open(filename, O_RDONLY); 114 | if (fd == -1) 115 | return 1; 116 | length = read(fd, buffer, sizeof(buffer) - 1); 117 | close(fd); 118 | buffer[length] = '\0'; 119 | if (length == 0) 120 | return 2; 121 | if (command_flag == 0) 122 | { 123 | /* 124 | * The command is near the beginning; we don't need to be able to 125 | * the entire stat file. 126 | */ 127 | p = strchr(buffer, '('); 128 | ++p; 129 | q = strchr(p, ')'); 130 | length = q - p; 131 | } 132 | else 133 | p = buffer; 134 | length = length < COMMANDLEN ? length : COMMANDLEN; 135 | strncpy(ion->command, p, length); 136 | ion->command[length] = '\0'; 137 | return 0; 138 | } 139 | 140 | struct io_node * 141 | get_ion(int pid) 142 | { 143 | struct io_node *c = head; 144 | 145 | while (c != NULL) 146 | { 147 | if (c->pid == pid) 148 | break; 149 | c = c->next; 150 | } 151 | return c; 152 | } 153 | 154 | int 155 | get_tcomm(struct io_node *ion) 156 | { 157 | int fd; 158 | int length; 159 | char filename[BUFFERLEN + 1]; 160 | char buffer[BUFFERLEN + 1]; 161 | char *p; 162 | char *q; 163 | 164 | length = snprintf(filename, BUFFERLEN, "%s/%d/stat", PROC, ion->pid); 165 | if (length == BUFFERLEN) 166 | printf("WARNING - filename length may be too big for buffer: %d\n", 167 | __LINE__); 168 | fd = open(filename, O_RDONLY); 169 | if (fd == -1) 170 | return 1; 171 | length = read(fd, buffer, sizeof(buffer) - 1); 172 | close(fd); 173 | /* 174 | * The command is near the beginning; we don't need to be able to 175 | * the entire stat file. 176 | */ 177 | p = strchr(buffer, '('); 178 | ++p; 179 | q = strchr(p, ')'); 180 | length = q - p; 181 | length = length < BUFFERLEN ? length : BUFFERLEN; 182 | strncpy(ion->command, p, length); 183 | ion->command[length] = '\0'; 184 | return 0; 185 | } 186 | 187 | struct io_node * 188 | insert_ion(struct io_node *ion) 189 | { 190 | struct io_node *c; 191 | struct io_node *p; 192 | 193 | /* Check the head of the list as a special case. */ 194 | if (ion->pid < head->pid) 195 | { 196 | ion->next = head; 197 | head = ion; 198 | return head; 199 | } 200 | 201 | c = head->next; 202 | p = head; 203 | while (c != NULL) 204 | { 205 | if (ion->pid < c->pid) 206 | { 207 | ion->next = c; 208 | p->next = ion; 209 | return head; 210 | } 211 | p = c; 212 | c = c->next; 213 | } 214 | 215 | /* Append to the end of the list. */ 216 | if (c == NULL) 217 | p->next = ion; 218 | 219 | return head; 220 | } 221 | 222 | void 223 | get_stats() 224 | { 225 | DIR *dir = opendir(PROC); 226 | struct dirent *ent; 227 | char filename[BUFFERLEN + 1]; 228 | char buffer[BUFFERLEN + 1]; 229 | 230 | char value[BUFFERLEN + 1]; 231 | 232 | /* Display column headers. */ 233 | if (hr_flag == 1) 234 | printf("%5s %5s %5s %8s %8s %5s %6s %7s %s\n", "pid", "rchar", "wchar", 235 | "syscr", "syscw", "reads", "writes", "cwrites", "command"); 236 | else if (kb_flag == 1) 237 | printf("%5s %8s %8s %8s %8s %8s %8s %8s %s\n", "pid", "rchar", "wchar", 238 | "syscr", "syscw", "rkb", "wkb", "cwkb", "command"); 239 | else if (mb_flag == 1) 240 | printf("%5s %8s %8s %8s %8s %8s %8s %8s %s\n", "pid", "rchar", "wchar", 241 | "syscr", "syscw", "rmb", "wmb", "cwmb", "command"); 242 | else 243 | printf("%5s %8s %8s %8s %8s %8s %8s %8s %s\n", "pid", "rchar", "wchar", 244 | "syscr", "syscw", "rbytes", "wbytes", "cwbytes", "command"); 245 | 246 | /* Loop through the process table and display a line per pid. */ 247 | while ((ent = readdir(dir)) != NULL) 248 | { 249 | int rc; 250 | int fd; 251 | int length; 252 | 253 | char *p; 254 | char *q; 255 | 256 | struct io_node *ion; 257 | struct io_node *old_ion; 258 | 259 | long long rchar; 260 | long long wchar; 261 | long long syscr; 262 | long long syscw; 263 | long long read_bytes; 264 | long long write_bytes; 265 | long long cancelled_write_bytes; 266 | 267 | if (!isdigit(ent->d_name[0])) 268 | continue; 269 | 270 | ion = new_ion(ent->d_name); 271 | 272 | if (command_flag == 1) 273 | rc = get_cmdline(ion); 274 | if (command_flag == 0 || rc != 0) 275 | /* If the full command line is not asked for or is empty... */ 276 | rc = get_tcomm(ion); 277 | 278 | if (rc != 0) 279 | { 280 | free(ion); 281 | continue; 282 | } 283 | 284 | /* Read 'io' file. */ 285 | length = snprintf(filename, BUFFERLEN, "%s/%s/io", PROC, ent->d_name); 286 | if (length == BUFFERLEN) 287 | printf("WARNING - filename length may be too big for buffer: %d\n", 288 | __LINE__); 289 | fd = open(filename, O_RDONLY); 290 | if (fd == -1) 291 | { 292 | free(ion); 293 | continue; 294 | } 295 | length = read(fd, buffer, sizeof(buffer) - 1); 296 | close(fd); 297 | buffer[length] = '\0'; 298 | 299 | /* Parsing the io file data. */ 300 | p = buffer; 301 | GET_VALUE(ion->rchar); 302 | GET_VALUE(ion->wchar); 303 | GET_VALUE(ion->syscr); 304 | GET_VALUE(ion->syscw); 305 | GET_VALUE(ion->read_bytes); 306 | GET_VALUE(ion->write_bytes); 307 | GET_VALUE(ion->cancelled_write_bytes); 308 | 309 | old_ion = get_ion(ion->pid); 310 | 311 | /* Display the pid's io data. */ 312 | if (old_ion != NULL) 313 | { 314 | rchar = ion->rchar - old_ion->rchar; 315 | wchar = ion->wchar - old_ion->wchar; 316 | syscr = ion->syscr - old_ion->syscr; 317 | syscw = ion->syscw - old_ion->syscw; 318 | read_bytes = ion->read_bytes - old_ion->read_bytes; 319 | write_bytes = ion->write_bytes - old_ion->write_bytes; 320 | cancelled_write_bytes = ion->cancelled_write_bytes - 321 | old_ion->cancelled_write_bytes; 322 | 323 | if (kb_flag == 1 && hr_flag == 0) 324 | { 325 | rchar = BTOKB(rchar); 326 | wchar = BTOKB(wchar); 327 | syscr = BTOKB(syscr); 328 | syscw = BTOKB(syscw); 329 | read_bytes = BTOKB(read_bytes); 330 | write_bytes = BTOKB(write_bytes); 331 | cancelled_write_bytes = BTOKB(cancelled_write_bytes); 332 | } 333 | else if (mb_flag == 1 && hr_flag == 0) 334 | { 335 | rchar = BTOMB(rchar); 336 | wchar = BTOMB(wchar); 337 | syscr = BTOMB(syscr); 338 | syscw = BTOMB(syscw); 339 | read_bytes = BTOMB(read_bytes); 340 | write_bytes = BTOMB(write_bytes); 341 | cancelled_write_bytes = BTOMB(cancelled_write_bytes); 342 | } 343 | 344 | if (!(idle_flag == 1 && rchar == 0 && wchar == 0 && syscr == 0 && 345 | syscw == 0 && read_bytes == 0 && write_bytes == 0 && 346 | cancelled_write_bytes == 0)) { 347 | if (hr_flag == 0) 348 | printf("%5d %8lld %8lld %8lld %8lld %8lld %8lld %8lld %s\n", 349 | ion->pid, 350 | rchar, 351 | wchar, 352 | syscr, 353 | syscw, 354 | read_bytes, 355 | write_bytes, 356 | cancelled_write_bytes, 357 | ion->command); 358 | else 359 | printf("%5d %5s %5s %8lld %8lld %5s %6s %7s %s\n", 360 | ion->pid, 361 | format_b(rchar), 362 | format_b(wchar), 363 | syscr, 364 | syscw, 365 | format_b(read_bytes), 366 | format_b(write_bytes), 367 | format_b(cancelled_write_bytes), 368 | ion->command); 369 | } 370 | } 371 | else if (idle_flag != 1) 372 | /* 373 | * No previous data, show 0's instead of calculating negatives 374 | * only if we are shoring idle processes. 375 | */ 376 | printf("%5d %8d %8d %8d %8d %8d %8d %8d %s\n", 377 | ion->pid, 0, 0, 0, 0, 0, 0, 0, ion->command); 378 | 379 | upsert_data(ion); 380 | } 381 | closedir(dir); 382 | return; 383 | } 384 | 385 | struct io_node * 386 | new_ion(char *pid) 387 | { 388 | struct io_node *ion; 389 | 390 | ion = (struct io_node *) malloc(sizeof(struct io_node)); 391 | bzero(ion, sizeof(struct io_node)); 392 | ion->pid = atoi(pid); 393 | 394 | return ion; 395 | } 396 | 397 | void 398 | upsert_data(struct io_node *ion) 399 | { 400 | struct io_node *n; 401 | 402 | /* List is empty. */ 403 | if (head == NULL) 404 | { 405 | head = ion; 406 | return; 407 | } 408 | 409 | /* Check if we have seen this pid before. */ 410 | n = head; 411 | while (n != NULL) 412 | { 413 | if (n->pid == ion->pid) 414 | { 415 | n->rchar = ion->rchar; 416 | n->wchar = ion->wchar; 417 | n->syscr = ion->syscr; 418 | n->syscw = ion->syscw; 419 | n->read_bytes = ion->read_bytes; 420 | n->write_bytes = ion->write_bytes; 421 | n->cancelled_write_bytes = ion->cancelled_write_bytes; 422 | /* 423 | * If the pids wrap, then the command may be different then before. 424 | */ 425 | strcpy(n->command, ion->command); 426 | free(ion); 427 | return; 428 | } 429 | n = n->next; 430 | } 431 | 432 | /* Add this pid to the list. */ 433 | head = insert_ion(ion); 434 | return; 435 | } 436 | 437 | void 438 | usage() 439 | { 440 | printf("usage: iopp -h|--help\n"); 441 | printf("usage: iopp [-ci] [-k|-m] [delay [count]]\n"); 442 | printf(" -c, --command display full command line\n"); 443 | printf(" -h, --help display help\n"); 444 | printf(" -i, --idle hides idle processes\n"); 445 | printf(" -k, --kilobytes display data in kilobytes\n"); 446 | printf(" -m, --megabytes display data in megabytes\n"); 447 | printf(" -u, --human-readable display data in kilo-, mega-, or giga-bytes\n"); 448 | } 449 | 450 | int 451 | main(int argc, char *argv[]) 452 | { 453 | int c; 454 | 455 | int delay = 0; 456 | int count = 0; 457 | int max_count = 1; 458 | 459 | while (1) 460 | { 461 | int option_index = 0; 462 | static struct option long_options[] = { 463 | { "command", no_argument, 0, 'c' }, 464 | { "help", no_argument, 0, 'h' }, 465 | { "human-readable", no_argument, 0, 'u' }, 466 | { "idle", no_argument, 0, 'i' }, 467 | { "kilobytes", no_argument, 0, 'k' }, 468 | { "megabytes", no_argument, 0, 'm' }, 469 | { 0, 0, 0, 0 } 470 | }; 471 | 472 | c = getopt_long(argc, argv, "chikmu", long_options, &option_index); 473 | if (c == -1) 474 | { 475 | /* Handle delay and count arguments. */ 476 | 477 | if (argc == optind) 478 | break; /* No additional arguments. */ 479 | else if ((argc - optind) == 1) 480 | { 481 | delay = atoi(argv[optind]); 482 | max_count = -1; 483 | } 484 | else if ((argc - optind) == 2) 485 | { 486 | delay = atoi(argv[optind]); 487 | max_count = atoi(argv[optind + 1]); 488 | } 489 | else 490 | { 491 | /* Too many additional arguments. */ 492 | usage(); 493 | return 3; 494 | } 495 | break; 496 | } 497 | 498 | switch (c) 499 | { 500 | case 'c': 501 | command_flag = 1; 502 | break; 503 | case 'h': 504 | usage(); 505 | return 0; 506 | case 'i': 507 | idle_flag = 1; 508 | break; 509 | case 'k': 510 | kb_flag = 1; 511 | break; 512 | case 'm': 513 | mb_flag = 1; 514 | break; 515 | case 'u': 516 | hr_flag = 1; 517 | break; 518 | default: 519 | usage(); 520 | return 2; 521 | } 522 | } 523 | 524 | while (max_count == -1 || count++ < max_count) 525 | { 526 | get_stats(); 527 | if (count != max_count) 528 | sleep(delay); 529 | } 530 | return 0; 531 | } 532 | --------------------------------------------------------------------------------