#!/usr/bin/ksh
 #
 # iopattern - print disk I/O pattern.
 #             Written using DTrace (Solaris 10 3/05).
 #
 # This prints details on the I/O access pattern for the disks, such as
 # percentage of events that were of a random or sequential nature.
 # By default totals for all disks are printed.
 #
 # $Id: iopattern 65 2007-10-04 11:09:40Z brendan $
 #
 # USAGE:	iopattern [-v] [-d device] [-f filename] [-m mount_point] 
 #			  [interval [count]]
 #
 #		       -v       	# print timestamp, string
 #		       -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,
 #		iopattern   	# default output, 1 second intervals
 #		iopattern 10  	# 10 second samples
 #		iopattern 5 12	# print 12 x 5 second samples
 #	        iopattern -m /  # snoop events on filesystem / only
 # 	
 # FIELDS:
 #		%RAN  		percentage of events of a random nature
 #		%SEQ 	 	percentage of events of a sequential nature
 #		COUNT		number of I/O events
 #		MIN		minimum I/O event size
 #		MAX		maximum I/O event size
 #		AVG		average I/O event size
 #		KR		total kilobytes read during sample
 #		KW		total kilobytes written during sample
 #		DEVICE		device name
 #		MOUNT		mount point
 #		FILE		filename
 #		TIME		timestamp, string
 # 
 # NOTES:
 #
 #  An event is considered random when the heads seek. This program prints
 #  the percentage of events that are random. The size of the seek is not
 #  measured - it's either random or not.
 #
 # SEE ALSO: iosnoop, iotop
 # 
 # IDEA: Ryan Matteson
 #
 # COPYRIGHT: Copyright (c) 2005 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]
 #
 # 25-Jul-2005	Brendan Gregg	Created this.
 # 25-Jul-2005	   "      "	Last update.
 #
 
 
 ##############################
 # --- Process Arguments ---
 #
 
 ### default variables
 opt_device=0; opt_file=0; opt_mount=0; opt_time=0
 filter=0; device=.; filename=.; mount=.; interval=1; count=-1
 
 ### process options
 while getopts d:f:hm:v name
 do
 	case $name in
 	d)	opt_device=1; device=$OPTARG ;;
 	f)	opt_file=1; filename=$OPTARG ;;
 	m)	opt_mount=1; mount=$OPTARG ;;
 	v)	opt_time=1 ;;
 	h|?)	cat <<-END >&2
 		USAGE: iopattern [-v] [-d device] [-f filename] [-m mount_point]
 		                 [interval [count]]
  
 		                -v              # print timestamp
 		                -d device       # instance name to snoop 
 		                -f filename     # snoop this file only
 		                -m mount_point  # this FS only 
 		   eg,
 		        iopattern         # default output, 1 second samples
 		        iopattern 10      # 10 second samples
 		        iopattern 5 12    # print 12 x 5 second samples
 		        iopattern -m /    # snoop events on filesystem / only
 		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
 
 
 #################################
 # --- Main Program, DTrace ---
 #
 /usr/sbin/dtrace -n '
  /*
   * Command line arguments
   */
  inline int OPT_time 	= '$opt_time';
  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'";
  
  #pragma D option quiet
 
  int last_loc[string];
 
  /*
   * Program start
   */
  dtrace:::BEGIN 
  {
         /* starting values */
 	diskcnt = 0;
 	diskmin = 0;
 	diskmax = 0;
 	diskran = 0;
 	diskr = 0;
 	diskw = 0;
         counts = COUNTER;
         secs = INTERVAL;
 	LINES = 20;
 	line = 0;
 	last_event[""] = 0;
  }
 
  /*
   * Print header
   */
  profile:::tick-1sec
  /line <= 0 /
  {
 	/* print optional headers */
 	OPT_time   ? printf("%-20s ", "TIME")  : 1;
 	OPT_device ? printf("%-9s ", "DEVICE") : 1;
 	OPT_mount  ? printf("%-12s ", "MOUNT") : 1;
 	OPT_file   ? printf("%-12s ", "FILE") : 1;
 
 	/* print header */
 	printf("%4s %4s %6s %6s %6s %6s %6s %6s\n",
 	    "%RAN", "%SEQ", "COUNT", "MIN", "MAX", "AVG", "KR", "KW");
 
 	line = LINES;
  }
 
  /*
   * Check event is being traced
   */
  io:genunix::done
  { 
 	/* default is to trace unless filtering */
 	self->ok = FILTER ? 0 : 1;
 
 	/* check each filter */
 	(OPT_device == 1 && DEVICE == args[1]->dev_statname)? self->ok = 1 : 1;
 	(OPT_file == 1 && FILENAME == args[2]->fi_pathname) ? self->ok = 1 : 1;
 	(OPT_mount == 1 && MOUNT == args[2]->fi_mount)  ? self->ok = 1 : 1;
  }
 
  /*
   * Process and Print completion
   */
  io:genunix::done
  /self->ok/
  {
 	/*
 	 * Save details
 	 */
 	this->loc = args[0]->b_blkno * 512;
 	this->pre = last_loc[args[1]->dev_statname];
 	diskr += args[0]->b_flags & B_READ ? args[0]->b_bcount : 0;
 	diskw += args[0]->b_flags & B_READ ? 0 : args[0]->b_bcount;
 	diskran += this->pre == this->loc ? 0 : 1;
 	diskcnt++;
 	diskmin = diskmin == 0 ? args[0]->b_bcount :
 	    (diskmin > args[0]->b_bcount ? args[0]->b_bcount : diskmin);
 	diskmax = diskmax < args[0]->b_bcount ? args[0]->b_bcount : diskmax;
 
 	/* save disk location */
 	last_loc[args[1]->dev_statname] = this->loc + args[0]->b_bcount;
 
 	/* cleanup */
 	self->ok = 0;
  }
 
  /*
   * Timer
   */
  profile:::tick-1sec
  {
 	secs--;
  }
 
  /*
   * Print Output
   */
  profile:::tick-1sec
  /secs == 0/
  {
 	/* calculate diskavg */
 	diskavg = diskcnt > 0 ? (diskr + diskw) / diskcnt : 0;
 
 	/* convert counters to Kbytes */
 	diskr /= 1024;
 	diskw /= 1024;
 
 	/* convert to percentages */
 	diskran = diskcnt == 0 ? 0 : (diskran * 100) / diskcnt;
 	diskseq = diskcnt == 0 ? 0 : 100 - diskran;
 
 	/* print optional fields */
 	OPT_time   ? printf("%-20Y ", walltimestamp) : 1;
 	OPT_device ? printf("%-9s ", DEVICE) : 1;
 	OPT_mount  ? printf("%-12s ", MOUNT) : 1;
 	OPT_file   ? printf("%-12s ", FILENAME) : 1;
 
 	/* print data */
 	printf("%4d %4d %6d %6d %6d %6d %6d %6d\n",
 	    diskran, diskseq, diskcnt, diskmin, diskmax, diskavg,
 	    diskr, diskw);
 
 	/* clear data */
 	diskmin = 0;
 	diskmax = 0;
 	diskcnt = 0;
 	diskran = 0;
 	diskr = 0;
 	diskw = 0;
 
 	secs = INTERVAL;
 	counts--;
 	line--;
  }
 
  /*
   * End of program
   */
  profile:::tick-1sec
  /counts == 0/
  {
 	exit(0);
  }
 '