#!/usr/bin/ksh
 #
 # iopending - Print a plot for the number of pending disk I/O events.
 #             Written using DTrace (Solaris 10 3/05).
 #
 # This is measuring disk events that have made it past system caches.
 # By plotting a distribution graph of the number of pending events, the
 # "serialness" or "parallelness" of disk behaviour can be distinguished.
 #
 # $Id: iopending 3 2007-08-01 10:50:08Z brendan $
 #
 # USAGE:	iopending [-c] [-d device] [-f filename] 
 #		          [-m mount_point] [interval [count]]
 #
 #		-c		# clear the screen
 #		-d device	# instance name to snoop (eg, dad0)
 #		-f filename	# full pathname of file to snoop
 #		-m mount_point	# this FS only (will skip raw events)
 #	eg,
 #		iopending   	# default output, 5 second intervals
 #		iopending 1  	# 1 second samples
 #		iopending -c	# clear the screen
 #		iopending 5 12	# print 12 x 5 second samples
 # 	
 # FIELDS:
 #		value		number of pending events, 0 == idle
 #		count		number of samples @ 1000 Hz
 #		load		1 min load average
 #		disk_r		total disk read Kbytes for sample
 #		disk_w		total disk write Kbytes for sample
 # 
 # SEE ALSO: iosnoop, iotop
 #
 # IDEA: Dr Rex di Bona (Sydney, Australia)
 #
 # COPYRIGHT: Copyright (c) 2005, 2006 Brendan Gregg.
 #
 # CDDL HEADER START
 #
 #  The contents of this file are subject to the terms of the
 #  Common Development and Distribution License, Version 1.0 only
 #  (the "License").  You may not use this file except in compliance
 #  with the License.
 #
 #  You can obtain a copy of the license at Docs/cddl1.txt
 #  or http://www.opensolaris.org/os/licensing.
 #  See the License for the specific language governing permissions
 #  and limitations under the License.
 #
 # CDDL HEADER END
 #
 # Author: Brendan Gregg  [Sydney, Australia]
 #
 # 01-Nov-2005	Brendan Gregg	Created this.
 # 20-Apr-2006	   "      "	Last update.
 #
 
 
 ##############################
 # --- Process Arguments ---
 #
 
 ### default variables
 opt_device=0; opt_file=0; opt_mount=0; opt_clear=0; 
 opt_def=1; filter=0; device=.; filename=.; mount=.
 interval=5; count=-1
 
 ### process options
 while getopts cd:f:hm: name
 do
 	case $name in
 	c)	opt_clear=1 ;;
 	d)	opt_device=1; device=$OPTARG ;;
 	f)	opt_file=1; filename=$OPTARG ;;
 	m)	opt_mount=1; mount=$OPTARG ;;
 	h|?)	cat <<-END >&2
 		USAGE: iopending [-c] [-d device] [-f filename]
 		                 [-m mount_point] [interval [count]]
  
 		                -c              # clear the screen
 		                -d device       # instance name to snoop 
 		                -f filename     # snoop this file only
 		                -m mount_point  # this FS only 
 		   eg,
 		        iopending         # default output, 5 second samples
 		        iopending 1       # 1 second samples
 		        iopending -m /    # snoop events on filesystem / only
 		        iopending 5 12    # print 12 x 5 second samples
 		END
 		exit 1
 	esac
 done
 
 shift $(( $OPTIND - 1 ))
 
 ### option logic
 if [[ "$1" > 0 ]]; then
         interval=$1; shift
 fi
 if [[ "$1" > 0 ]]; then
         count=$1; shift
 fi
 if (( opt_device || opt_mount || opt_file )); then
 	filter=1
 fi
 if (( opt_clear )); then
         clearstr=`clear`
 else
         clearstr=.
 fi
 
 
 
 #################################
 # --- Main Program, DTrace ---
 #
 /usr/sbin/dtrace -n '
  /*
   * Command line arguments
   */
  inline int OPT_def 	= '$opt_def';
  inline int OPT_clear 	= '$opt_clear';
  inline int OPT_device 	= '$opt_device';
  inline int OPT_mount 	= '$opt_mount';
  inline int OPT_file 	= '$opt_file';
  inline int INTERVAL 	= '$interval';
  inline int COUNTER 	= '$count';
  inline int FILTER 	= '$filter';
  inline string DEVICE 	= "'$device'";
  inline string FILENAME = "'$filename'";
  inline string MOUNT 	= "'$mount'";
  inline string CLEAR 	= "'$clearstr'";
 
  inline int MAX_PENDING = 32;	/* max pending value */
  
  #pragma D option quiet
 
  /*
   * Print header
   */
  dtrace:::BEGIN 
  {
         /* starting values */
         counts = COUNTER;
         secs = INTERVAL;
         disk_r = 0;
         disk_w = 0;
         pending = 0;
 
         printf("Tracing... Please wait.\n");
  }
 
  /*
   * Check event is being traced
   */
  io:genunix::start,
  io:genunix::done 
  { 
 	/* default is to trace unless filtering, */
 	this->ok = FILTER ? 0 : 1;
 
 	/* check each filter, */
 	(OPT_device == 1 && DEVICE == args[1]->dev_statname)? this->ok = 1 : 1;
 	(OPT_file == 1 && FILENAME == args[2]->fi_pathname) ? this->ok = 1 : 1;
 	(OPT_mount == 1 && MOUNT == args[2]->fi_mount)  ? this->ok = 1 : 1;
  }
 
  /*
   * Store entry details
   */
  io:genunix::start
  /this->ok/
  {
 	/* track bytes */
 	disk_r += args[0]->b_flags & B_READ ? args[0]->b_bcount : 0;
 	disk_w += args[0]->b_flags & B_READ ? 0 : args[0]->b_bcount;
 
 	/* increase event pending count */
 	pending++;
  }
 
  /*
   * Process and Print completion
   */
  io:genunix::done
  /this->ok/
  {
 	/* decrease event pending count */
 	pending--;
  }
 
  /*
   * Prevent pending from underflowing
   * this can happen if this program is started during disk events.
   */
  io:genunix::done
  /pending < 0/
  {
 	pending = 0;
  }
 
  /*
   * Timer
   */
  profile:::tick-1sec
  {
 	secs--;
  }
 
  profile:::profile-1000hz
  {
 	@out = lquantize(pending, 0, MAX_PENDING, 1);
  }
 
  /*
   * Print Report
   */
  profile:::tick-1sec
  /secs == 0/
  {
 	/* fetch 1 min load average */
 	this->load1a  = `hp_avenrun[0] / 65536;
 	this->load1b  = ((`hp_avenrun[0] % 65536) * 100) / 65536;
 
 	/* convert counters to Kbytes */
 	disk_r /= 1024;
 	disk_w /= 1024;
 
 	/* print status */
 	OPT_clear ? printf("%s", CLEAR) : 1;
 	printf("%Y,  load: %d.%02d,  disk_r: %6d KB,  disk_w: %6d KB",
 	    walltimestamp, this->load1a, this->load1b, disk_r, disk_w);
 
 	/* print output */
 	printa(@out);
 
 	/* clear data */
 	trunc(@out);
 	disk_r = 0;
 	disk_w = 0;
 	secs = INTERVAL;
 	counts--;
  }
 
  /*
   * End of program
   */
  profile:::tick-1sec
  /counts == 0/
  {
 	exit(0);
  }
 
  /*
   * Cleanup for Ctrl-C
   */
  dtrace:::END
  {
 	trunc(@out);
  }
 '