├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── debian ├── changelog ├── control ├── copyright ├── diskgraph.manpages ├── rules └── source │ └── format ├── diskgraph.1 ├── diskgraph.c └── images └── screenshot0.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | 7 | # Executable 8 | diskgraph 9 | 10 | # Debug files 11 | *.dSYM/ 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bram Stolk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= cc 2 | CFLAGS += -D_POSIX_C_SOURCE=199309L -std=c99 -Wall -Wextra 3 | LDFLAGS += 4 | 5 | TARGET = diskgraph 6 | SRC = diskgraph.c 7 | OBJ = $(SRC:.c=.o) 8 | 9 | DISTFILES=\ 10 | Makefile \ 11 | diskgraph.c \ 12 | diskgraph.1 \ 13 | README.md \ 14 | LICENSE \ 15 | images \ 16 | debian 17 | 18 | all: $(TARGET) 19 | 20 | $(TARGET): $(OBJ) 21 | $(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LDFLAGS) 22 | 23 | .c.o: 24 | $(CC) $(CFLAGS) -c $< -o $@ 25 | 26 | clean: 27 | $(RM) *.o $(TARGET) 28 | @echo All clean 29 | 30 | install: diskgraph 31 | install -d ${DESTDIR}/usr/bin 32 | install -m 755 diskgraph ${DESTDIR}/usr/bin/ 33 | 34 | uninstall: 35 | rm -f ${DESTDIR}/usr/bin/distgraph 36 | 37 | tarball: 38 | tar cvzf ../diskgraph_1.2.orig.tar.gz $(DISTFILES) 39 | 40 | packageupload: 41 | debuild -S 42 | debsign ../diskgraph_1.2-1_source.changes 43 | dput --force ppa:b-stolk/ppa ../diskgraph_1.2-1_source.changes 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diskgraph 2 | Monitor for disk IO 3 | 4 | ![screenshot](images/screenshot0.png "screenshot") 5 | 6 | 7 | ## Introduction 8 | 9 | The diskgraph tool will graph disk IO under linux, in a terminal. 10 | 11 | Examples: 12 | 13 | **$ ./diskgraph /dev/nvme0n1** 14 | 15 | 16 | **$ ./diskgraph /dev/sda** 17 | 18 | You can also leave out the `dev` prefix and do: 19 | 20 | **$ ./diskgraph sda** 21 | 22 | 23 | Periodically (500ms intervals) it will read the statistics from /sys/block/DEVICE/stat and see how many sectors are read and written since last sample. 24 | 25 | These are converted to bandwidth, by dividing by time. 26 | 27 | It also shows (in orange) the number of operations that are in-flight at the moment the sample was taken. 28 | 29 | ## Keys 30 | 31 | Press ESCAPE or Q to exit diskgraph. 32 | 33 | ## Known issues 34 | 35 | * Shows garbage on terminals that do not support 24 bit colour. 36 | * Does not work on older linux kernels. 37 | * Missing manual page. 38 | * Assumes 512 byte sectors. 39 | * Uses a lot of bandwidth when used over network, due to frequent screen-redraws. To mitigate: use small term size. 40 | 41 | ## Copyright 42 | 43 | diskgraph is (c)2021 by Bram Stolk and licensed under the MIT license. 44 | Thank you to PQCraft for improving resize functionality and other changes. 45 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | diskgraph (1.2-1) plucky; urgency=medium 2 | 3 | * New build for plucky puffin. 4 | 5 | -- Bram Stolk Sun, 25 May 2025 14:07:50 -0700 6 | 7 | diskgraph (1.1-1) kinetic; urgency=medium 8 | 9 | * Properly clear screen. 10 | 11 | -- Bram Stolk Thu, 15 Dec 2022 14:04:46 -0800 12 | 13 | diskgraph (1.0-1) kinetic; urgency=medium 14 | 15 | * Initial release. 16 | 17 | -- Bram Stolk Wed, 07 Sep 2022 17:24:00 -0700 18 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: diskgraph 2 | Section: contrib/utils 3 | Priority: optional 4 | Maintainer: Bram Stolk 5 | Rules-Requires-Root: no 6 | Build-Depends: 7 | debhelper-compat (= 13) 8 | Standards-Version: 4.6.1 9 | Homepage: https://github.com/stolk/diskgraph 10 | 11 | Package: diskgraph 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Graphs live view of R/W bandwidth of a disk drive. 15 | Shows a live graph of the R/W bandwidth of a disk drive along with how many IO operations are in flight. 16 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: diskgraph 3 | Upstream-Contact: Bram Stolk 4 | Source: https://github.com/stolk/diskgraph 5 | 6 | Files: * 7 | Copyright: 2021-2022 Bram Stolk 8 | License: MIT 9 | 10 | License: MIT 11 | MIT License 12 | . 13 | Copyright (c) 2022 Bram Stolk 14 | . 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | . 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | . 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | -------------------------------------------------------------------------------- /debian/diskgraph.manpages: -------------------------------------------------------------------------------- 1 | diskgraph.1 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # Output every command that modifies files on the build system. 4 | export DH_VERBOSE = 1 5 | 6 | 7 | # See FEATURE AREAS in dpkg-buildflags(1). 8 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 9 | 10 | # See ENVIRONMENT in dpkg-buildflags(1). 11 | # Package maintainers to append CFLAGS. 12 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 13 | # Package maintainers to append LDFLAGS. 14 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 15 | 16 | 17 | %: 18 | dh $@ 19 | 20 | 21 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /diskgraph.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" (C) Copyright 2022 Bram Stolk , 3 | .\" 4 | .\" First parameter, NAME, should be all caps 5 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 6 | .\" other parameters are allowed: see man(7), man(1) 7 | .TH DISKGRAPH 1 "September 7 2022" 8 | .\" Please adjust this date whenever revising the manpage. 9 | .\" 10 | .\" Some roff macros, for reference: 11 | .\" .nh disable hyphenation 12 | .\" .hy enable hyphenation 13 | .\" .ad l left justify 14 | .\" .ad b justify to both left and right margins 15 | .\" .nf disable filling 16 | .\" .fi enable filling 17 | .\" .br insert line break 18 | .\" .sp insert n+1 empty lines 19 | .\" for manpage-specific macros, see man(7) 20 | .SH NAME 21 | diskgraph \- program to graph disk activity 22 | .SH SYNOPSIS 23 | .B diskgraph /dev/nvme0n1 24 | .SH DESCRIPTION 25 | This manual page documents the 26 | .B diskgraph 27 | command. 28 | .PP 29 | \fBdiskgraph\fP is a program that that shows a live graph of disk activity. 30 | .br 31 | It shows the READ bandwidth, the WRITE bandwith and the number of operations that are in flight. 32 | .br 33 | .SH ARGUMENTS 34 | The first and only parameter is a disk device like /dev/sda or /dev/nvme0n1 or similar. 35 | .br 36 | .SH SEE ALSO 37 | .br 38 | -------------------------------------------------------------------------------- /diskgraph.c: -------------------------------------------------------------------------------- 1 | // diskgraph.c 2 | // 3 | // by Abraham Stolk. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | static int termw = 0, termh = 0; 19 | static int doubleres = 0; 20 | static int blend = 1; 21 | static unsigned char termbg[3] = { 0,0,0 }; 22 | 23 | static int imw = 0; 24 | static int imh = 0; 25 | static uint32_t* im = 0; 26 | static char* legend = 0; 27 | 28 | static char postscript[256]; 29 | 30 | static int resized = 1; 31 | 32 | static double period = 0; 33 | 34 | 35 | static void get_terminal_size(void) 36 | { 37 | struct winsize tmp; 38 | memset(&tmp, 0, sizeof(tmp)); 39 | const int rv = ioctl(STDIN_FILENO, TIOCGWINSZ, &tmp); 40 | if (rv<0) 41 | { 42 | perror("ioctl TIOCGWINSZ"); 43 | fprintf(stderr,"Not running in a terminal.\n"); 44 | exit(4); 45 | } 46 | termw = tmp.ws_col; 47 | termh = tmp.ws_row; 48 | } 49 | 50 | 51 | static void set_console_mode() 52 | { 53 | doubleres = 1; 54 | } 55 | 56 | 57 | // History of statistics. 58 | #define MAXHIST 320 59 | typedef uint32_t measurement_t[3]; 60 | measurement_t hist[ MAXHIST ]; 61 | uint32_t head = 0; 62 | uint32_t tail = 0; 63 | 64 | uint64_t maxbw = 8192; // top of the left-hand scale: bandwidth in sectors per second. 65 | uint32_t maxif = 16; // top of the right-hand scale: operation count. 66 | 67 | 68 | static void setup_image(void) 69 | { 70 | if (im) free(im); 71 | if (legend) free(legend); 72 | 73 | imw = termw; 74 | imh = 2 * (termh-1); 75 | const size_t sz = imw * imh * 4; 76 | im = (uint32_t*) malloc(sz); 77 | memset( im, 0x00, sz ); 78 | 79 | legend = (char*) malloc( imw * (imh/2) ); 80 | memset( legend, 0x00, imw * (imh/2) ); 81 | 82 | // Draw border into image. 83 | for ( int y = 0; y 0 ); 247 | 248 | period = 0.001 * elapsed_ms_since_last_call(); 249 | period = period < 0.001 ? 1 : period; 250 | 251 | uint32_t v[15]; 252 | int numv = sscanf 253 | ( 254 | info, 255 | "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u", 256 | v+0,v+1,v+2,v+3,v+4,v+5,v+6,v+7,v+8,v+9,v+10,v+11,v+12,v+13,v+14 257 | ); 258 | if ( numv != 15 ) 259 | { 260 | fprintf 261 | ( 262 | stderr, 263 | "Did not find all the fields in %s that were expected.\n" 264 | "Most likely, your kernel is too old to work with diskgraph.\n", 265 | fname 266 | ); 267 | exit(3); 268 | } 269 | 270 | uint32_t rd = v[2]; // number of sectors read. 271 | uint32_t wr = v[6]; // number of sectors written. 272 | if ( firstrun ) 273 | { 274 | cur_rd = rd; 275 | cur_wr = wr; 276 | firstrun = 0; 277 | } 278 | dif_rd = rd - cur_rd; // number of sectors read since last time. 279 | dif_wr = wr - cur_wr; // number of sectors written since last time. 280 | cur_rd = rd; 281 | cur_wr = wr; 282 | cur_if = v[8]; // number of operations in flight. 283 | 284 | hist[tail][0] = (uint32_t) ( dif_rd / period ); // sectors read per second. 285 | hist[tail][1] = (uint32_t) ( dif_wr / period ); // sectors written per second. 286 | hist[tail][2] = cur_if; 287 | tail = (tail + 1) % MAXHIST; 288 | if ( tail == head ) 289 | head = (head + 1) % MAXHIST; 290 | } 291 | 292 | 293 | int histsz(void) 294 | { 295 | int sz = tail - head; 296 | sz = sz < 0 ? sz + MAXHIST : sz; 297 | return sz; 298 | } 299 | 300 | 301 | static void set_postscript(const char* devname) 302 | { 303 | char fname[128]; 304 | char nm[128]; 305 | memset(nm, 1, sizeof(nm)); 306 | snprintf( fname, sizeof(fname), "/sys/class/block/%s/device/model", devname ); 307 | FILE* f = fopen( fname, "rb" ); 308 | 309 | if ( f ) 310 | { 311 | const size_t l = fread( nm, 1, sizeof(nm), f ); 312 | if ( l>0 && l /dev/null 2> /dev/null") ) 343 | { 344 | exit(1); 345 | } 346 | if ( argc != 2 ) 347 | { 348 | fprintf( stderr, "Usage: %s devicename\n", argv[0] ); 349 | exit(1); 350 | } 351 | const char* devname = argv[1]; 352 | if ( !strncmp( devname, "/dev/", 5 ) ) 353 | devname += 5; 354 | char fname[128]; 355 | snprintf( fname, sizeof(fname), "/sys/block/%s/stat", devname ); 356 | FILE* f = fopen(fname, "rb"); 357 | if ( !f ) 358 | { 359 | fprintf( stderr, "Failed to open %s: %s.\n", fname, strerror(errno) ); 360 | exit(2); 361 | } 362 | fclose(f); 363 | 364 | get_stats( fname ); 365 | 366 | set_postscript( devname ); 367 | 368 | // Parse environment variable for terminal background colour. 369 | const char* imcatbg = getenv( "IMCATBG" ); 370 | if ( imcatbg ) 371 | { 372 | const int bg = strtol( imcatbg + 1, 0, 16 ); 373 | termbg[ 2 ] = ( bg >> 0 ) & 0xff; 374 | termbg[ 1 ] = ( bg >> 8 ) & 0xff; 375 | termbg[ 0 ] = ( bg >> 16 ) & 0xff; 376 | blend = 1; 377 | } 378 | 379 | // Step 0: Windows cmd.exe needs to be put in proper console mode. 380 | set_console_mode(); 381 | 382 | 383 | enableRawMode(); 384 | 385 | // Listen to changes in terminal size 386 | struct sigaction sa; 387 | sigemptyset( &sa.sa_mask ); 388 | sa.sa_flags = 0; 389 | sa.sa_handler = sigwinchHandler; 390 | if ( sigaction( SIGWINCH, &sa, 0 ) == -1 ) 391 | perror( "sigaction" ); 392 | 393 | int done = 0; 394 | while ( !done ) 395 | { 396 | if ( resized ) 397 | { 398 | printf(SETBG "0;0;0m"); 399 | printf(CLEARSCREEN); 400 | get_terminal_size(); 401 | setup_image(); 402 | resized = 0; 403 | } 404 | 405 | char c; 406 | const int numr = read( STDIN_FILENO, &c, 1 ); 407 | if ( numr == 1 && ( c == 27 || c == 'q' || c == 'Q' ) ) 408 | done = 1; 409 | 410 | int hsz = histsz(); 411 | int overflow_bw = 0; 412 | int overflow_if = 0; 413 | 414 | uint32_t quarter_bw = (uint32_t) ( maxbw * 512UL / ( 4UL * 1024 * 1024 ) ); // A quarter of the max bandwidth, in bytes/sec. 415 | uint32_t quarter_if = maxif / 4; // A quarter of the max operations count. 416 | 417 | assert( quarter_bw > 0 ); 418 | 419 | snprintf( legend + imw * ( 1) + 1, 80, "%d MiB/s", 4 * quarter_bw ); 420 | snprintf( legend + imw * (imh / 8 * 1) + 1, 80, "%d MiB/s", 3 * quarter_bw ); 421 | snprintf( legend + imw * (imh / 8 * 2) + 1, 80, "%d MiB/s", 2 * quarter_bw ); 422 | snprintf( legend + imw * (imh / 8 * 3) + 1, 80, "%d MiB/s", 1 * quarter_bw ); 423 | 424 | char lab0[16]; 425 | char lab1[16]; 426 | char lab2[16]; 427 | char lab3[16]; 428 | snprintf( lab0, sizeof(lab0), "%d ops", quarter_if * 4 ); 429 | snprintf( lab1, sizeof(lab1), "%d ops", quarter_if * 3 ); 430 | snprintf( lab2, sizeof(lab2), "%d ops", quarter_if * 2 ); 431 | snprintf( lab3, sizeof(lab3), "%d ops", quarter_if * 1 ); 432 | 433 | snprintf( legend + imw * ( 1) + (imw-1-strlen(lab0)), 80, "%s", lab0 ); 434 | snprintf( legend + imw * (imh / 8 * 1) + (imw-1-strlen(lab1)), 80, "%s", lab1 ); 435 | snprintf( legend + imw * (imh / 8 * 2) + (imw-1-strlen(lab2)), 80, "%s", lab2 ); 436 | snprintf( legend + imw * (imh / 8 * 3) + (imw-1-strlen(lab3)), 80, "%s", lab3 ); 437 | 438 | for ( int32_t i = 1; i maxbw || wr >= maxbw ) 460 | overflow_bw = 1; 461 | if ( op > maxif ) 462 | overflow_if = 1; 463 | uint32_t c = (a<<24); 464 | if ( i == (int)rd_l ) c = c_g; 465 | if ( i == (int)wr_l ) c = c_r; 466 | if ( i == (int)op_l ) c = c_o; 467 | im[ y * imw + x ] = c; 468 | } 469 | } 470 | } 471 | 472 | if ( overflow_bw ) maxbw *= 2; 473 | 474 | if ( overflow_if ) maxif *= 2; 475 | 476 | get_stats(fname); 477 | 478 | printf( CURSORHOME ); 479 | print_image_double_res( imw, imh, (unsigned char*) im, legend ); 480 | 481 | printf( "%s", postscript ); 482 | fflush( stdout ); 483 | 484 | const int NS_PER_MS = 1000000; 485 | struct timespec ts = { 0, 200 * NS_PER_MS }; 486 | nanosleep( &ts, 0 ); 487 | } 488 | 489 | free(im); 490 | 491 | printf( RESETALL ); 492 | printf( CLEARSCREEN ); 493 | 494 | return 0; 495 | } 496 | 497 | -------------------------------------------------------------------------------- /images/screenshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolk/diskgraph/e5579fedc6e86b5145bda6255a128f45120b08be/images/screenshot0.png --------------------------------------------------------------------------------