Subject: [PATCH] [FEAT 82722] dasdstat: Add new zconf tool dasdstat
From: Stefan Weinhuber <wein@de.ibm.com>

Summary:     dasdstat: Add new zconf tool dasdstat
Description: The dasdstat command provides easy access to the debugfs based
             statistics of the DASD device driver.
Problem-ID:  82722
---
 README           |    1 
 zconf/Makefile   |    4 
 zconf/dasdstat   |  464 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 zconf/dasdstat.8 |   97 +++++++++++
 4 files changed, 564 insertions(+), 2 deletions(-)

--- a/README
+++ b/README
@@ -115,6 +115,7 @@ s390-tools (1.15.0)
                    blacklist.
      - lsmem:      Display the online status of the available memory.
      - chmem:      Set hotplug memory online or offline.
+     - dasdstat:   Configure and format the debugfs based DASD statistics data.
 
    * dumpconf:
      Allows to configure the dump device used for system dump in case a kernel
--- /dev/null
+++ b/zconf/dasdstat
@@ -0,0 +1,464 @@
+#!/bin/bash
+#
+# Copyright IBM Corp. 2011
+#
+# dasdstat
+# Reads dasd statistics data from debugfs and prints it as formatted table
+#
+# Author(s): Stefan Weinhuber <wein@de.ibm.com>
+#
+
+function print_usage() {
+	cat <<-EOD
+Usage: $CMD <options> [<statistic>]
+
+<options> ::=
+        -e|--enable
+                Enable the statistics.
+        -d|--disable
+                Disable the statistics.
+        -r|--reset
+                Reset the statistics.
+        -i|--directory <directory>
+                Specify the directory in which the statistics can be found.
+        -h|--help
+                Print this text and exit.
+        -l|--long
+                Print more detailed information, e.g differentiate between
+                read and write requests.
+        -c|--columns <number>
+                Format the output in a table with the given number of columns.
+        -w|--column-width <width>
+                Set the minimum width of the columns in the output table.
+        -V|--verbose
+                Print more verbose information.
+        -v|--version
+                Show tools and command version.
+
+<statistic>
+        Limit operation to one or more statistic.
+	EOD
+}
+
+function print_version()
+{
+	echo "$CMD: version %S390_TOOLS_VERSION%"
+	echo "Copyright IBM Corp. 2011"
+}
+
+function unset_known_variables() {
+	unset start_time
+	unset total_requests
+	unset total_sectors
+	unset total_pav
+	unset total_hpf
+	unset histogram_sectors
+	unset histogram_io_times
+	unset histogram_io_times_weighted
+	unset histogram_time_build_to_ssch
+	unset histogram_time_ssch_to_irq
+	unset histogram_time_ssch_to_irq_weighted
+	unset histogram_time_irq_to_end
+	unset histogram_ccw_queue_length
+	unset total_read_requests
+	unset total_read_sectors
+	unset total_read_pav
+	unset total_read_hpf
+	unset histogram_read_sectors
+	unset histogram_read_times
+	unset histogram_read_time_build_to_ssch
+	unset histogram_read_time_ssch_to_irq
+	unset histogram_read_time_irq_to_end
+	unset histogram_read_ccw_queue_length
+}
+
+function print_array()
+{
+	local width=$1
+	shift
+	local linebreak=$1
+	shift
+	local i=1
+	for element in $*
+	do
+		printf "%${width}s" $element
+		(( 0 == i++ % linebreak )) && printf "\n"
+	done
+	# add an extra line break if we do not already have one
+	(( 0 != (i - 1) % linebreak )) && printf "\n"
+}
+
+function subtract_array()
+{
+	local -a a=( $1 )
+	local -a b=( $2 )
+	local -a c
+	local i
+	local cnt=${#a[@]}
+	for (( i = 0 ; i < cnt ; i++ ))
+	do
+		(( c[i] = a[i] - b[i] ))
+	done
+	echo -n ${c[*]}
+}
+
+function print_line()
+{
+	local i
+	for (( i = 0 ; i < $1 ; i++ ))
+	do
+		echo -n '-'
+	done
+	echo
+}
+
+HEADERSEXP=" __<4    ___8    __16    __32    __64    _128    _256    _512 \
+             __1k    __2k    __4k    __8k    _16k    _32k    _64k    128k \
+             _256    _512    __1M    __2M    __4M    __8M    _16M    _32M \
+             _64M    128M    256M    512M    __1G    __2G    __4G    _>4G"
+
+HEADERSLIN=" ___0    ___1    ___2    ___3    ___4    ___5    ___6    ___7 \
+             ___8    ___9    __10    __11    __12    __13    __14    __15 \
+             __16    __17    __18    __19    __20    __21    __22    __23 \
+             __24    __25    __26    __27    __28    __29    __30    __31"
+
+
+function print_format_standard()
+{
+	local converted_time=$(date -d @$start_time)
+	# all numbers in the histograms below are smaller or equal to the
+	# total_requests. So we use that number to determine the column width.
+	local width=${#total_requests}
+	(( width++ ))
+	(( width < 5 )) && width=5
+
+	if [[ -n $COLUMN_WIDTH ]] && [[ $width -lt $COLUMN_WIDTH ]]
+	then
+		width=$COLUMN_WIDTH
+	fi
+
+	local linebreak=$NUMBER_COLUMNS
+	local statname="$1"
+	local tablewidth
+	(( tablewidth = width * linebreak ))
+
+	# Note: This function does not print the histogram_io_times_weighted
+	#       and histogram_time_ssch_to_irq_weighted because the interpretation
+	#       of these histograms is not intuitive and may lead to confusion.
+	print_line $tablewidth
+	printf "statistics data for statistic: %s\n" "$statname"
+	printf "start time of data collection: %s\n\n" "$converted_time"
+
+	printf "%d dasd I/O requests\n" ${total_requests[*]}
+	printf "with %u sectors(512B each)\n" ${total_sectors[*]}
+	printf "%d requests used a PAV alias device\n" ${total_pav[*]}
+	printf "%d requests used HPF\n" ${total_hpf[*]}
+	print_array $width $linebreak $HEADERSEXP
+	printf "Histogram of sizes (512B secs)\n"
+	print_array $width $linebreak ${histogram_sectors[*]}
+	printf "Histogram of I/O times (microseconds)\n"
+	print_array $width $linebreak ${histogram_io_times[*]}
+	printf "Histogram of I/O time till ssch\n"
+	print_array $width $linebreak ${histogram_time_build_to_ssch[*]}
+	printf "Histogram of I/O time between ssch and irq\n"
+	print_array $width $linebreak ${histogram_time_ssch_to_irq[*]}
+	printf "Histogram of I/O time between irq and end\n"
+	print_array $width $linebreak ${histogram_time_irq_to_end[*]}
+	printf "# of req in chanq at enqueuing (0..31) \n"
+	print_array $width $linebreak $HEADERSLIN
+	print_array $width $linebreak ${histogram_ccw_queue_length[*]}
+
+	if [[ $OUTPUT == "short" ]]
+	then
+		print_line $tablewidth
+		return
+	fi
+
+	printf "\n%d dasd I/O read requests\n" ${total_read_requests[*]}
+	printf "with %u sectors(512B each)\n" ${total_read_sectors[*]}
+	printf "%d requests used a PAV alias device\n" ${total_read_pav[*]}
+	printf "%d requests used HPF\n" ${total_read_hpf[*]}
+	print_array $width $linebreak $HEADERSEXP
+	printf "Histogram of sizes (512B secs)\n"
+	print_array $width $linebreak ${histogram_read_sectors[*]}
+	printf "Histogram of I/O times (microseconds)\n"
+	print_array $width $linebreak ${histogram_read_times[*]}
+	printf "Histogram of I/O time till ssch\n"
+	print_array $width $linebreak ${histogram_read_time_build_to_ssch[*]}
+	printf "Histogram of I/O time between ssch and irq\n"
+	print_array $width $linebreak ${histogram_read_time_ssch_to_irq[*]}
+	printf "Histogram of I/O time between irq and end\n"
+	print_array $width $linebreak ${histogram_read_time_irq_to_end[*]}
+	printf "# of req in chanq at enqueuing (0..31) \n"
+	print_array $width $linebreak $HEADERSLIN
+	print_array $width $linebreak ${histogram_read_ccw_queue_length[*]}
+
+	printf "\n%d dasd I/O write requests\n" $(( total_requests[0] - total_read_requests[0] ))
+	printf "with %u sectors(512B each)\n" $(( total_sectors[0] - total_read_sectors[0] ))
+	printf "%d requests used a PAV alias device\n" $(( total_pav[0] - total_read_pav[0] ))
+	printf "%d requests used HPF\n" $(( total_hpf[0] - total_read_hpf[0] ))
+	print_array $width $linebreak $HEADERSEXP
+	printf "Histogram of sizes (512B secs)\n"
+	print_array $width $linebreak $(subtract_array "${histogram_sectors[*]}" "${histogram_read_sectors[*]}")
+	printf "Histogram of I/O times (microseconds)\n"
+	print_array $width $linebreak $(subtract_array "${histogram_io_times[*]}" "${histogram_read_times[*]}")
+	printf "Histogram of I/O time till ssch\n"
+	print_array $width $linebreak $(subtract_array "${histogram_time_build_to_ssch[*]}" "${histogram_read_time_build_to_ssch[*]}")
+	printf "Histogram of I/O time between ssch and irq\n"
+	print_array $width $linebreak $(subtract_array "${histogram_time_ssch_to_irq[*]}" "${histogram_read_time_ssch_to_irq[*]}")
+	printf "Histogram of I/O time between irq and end\n"
+	print_array $width $linebreak $(subtract_array "${histogram_time_irq_to_end[*]}" "${histogram_read_time_irq_to_end[*]}")
+	printf "# of req in chanq at enqueuing (0..31) \n"
+	print_array $width $linebreak $HEADERSLIN
+	print_array $width $linebreak $(subtract_array "${histogram_ccw_queue_length[*]}" "${histogram_read_ccw_queue_length[*]}")
+
+	print_line $tablewidth
+}
+
+function read_stat_data() {
+	myfile="$1"
+	while read -d ' ' token
+	do
+		read -a $token
+	done < $myfile
+	# differentiate I/O error from disabled statistic
+	if [[ -n $total_requests ]] || [[ "$token" == "disabled" ]]
+	then
+		return 0
+	else
+		return 1
+	fi
+}
+
+function print_stat() {
+	myfile="$1"
+	if [[ ! -f "$myfile" ]]
+	then
+		print_line 80
+		echo "$CMD: Statistic \"$myfile\" does not exist" >&2
+		print_line 80
+		echo
+		return
+	fi
+	unset_known_variables
+	if read_stat_data "$myfile"
+	then
+		if [[ -z "$total_requests" ]]
+		then
+			if [[ "$VERBOSE" == "true" ]]
+			then
+				print_line 80
+				echo "statistics \"$f\" are disabled"
+				print_line 80
+				echo
+			fi
+		else
+			print_format_standard $f
+			echo
+		fi
+	else
+		print_line 80
+		echo "$CMD: Could not read statistic \"$myfile\" " >&2
+		print_line 80
+		echo
+	fi
+}
+
+function enable_stat() {
+	myfile="$1"
+	if [[ ! -f "$myfile" ]]
+	then
+		echo "$CMD: Statistic \"$myfile\" does not exist" >&2
+		return
+	fi
+	if echo on > "$myfile"
+	then
+		echo "enable statistic \"$myfile\""
+	else
+		echo "$CMD: Failed to enable statistic \"$myfile\"" >&2
+	fi
+}
+
+function disable_stat() {
+	myfile="$1"
+	if [[ ! -f "$myfile" ]]
+	then
+		echo "$CMD: Statistic \"$myfile\" does not exist" >&2
+		return
+	fi
+	if echo off > "$myfile"
+	then
+		echo "disable statistic \"$myfile\""
+	else
+		echo "$CMD: Failed to disable statistic \"$myfile\""
+	fi
+}
+
+function reset_stat() {
+	myfile="$1"
+	if [[ ! -f "$myfile" ]]
+	then
+		echo "$CMD: Statistic \"$myfile\" does not exist" >&2
+		return
+	fi
+	if echo reset > "$myfile"
+	then
+		echo "reset statistic \"$myfile\""
+	else
+		echo "$CMD: Failed to reset statistic \"$myfile\"" >&2
+	fi
+}
+
+function verbose_msg() {
+	if [[ "$VERBOSE" == "true" ]]
+	then
+		echo "$*"
+	fi
+}
+
+
+# Evaluating command line options
+CMD=$(basename $0)
+DASD_STATISTICS_DIR=""
+ALLFILES=""
+VERBOSE=false
+OUTPUT="short"
+NUMBER_COLUMNS=16
+COLUMN_WIDTH=
+ACTION="print"
+while [ $# -gt 0 ]; do
+	case $1 in
+	--help|-h)
+		print_usage
+		exit 0
+		;;
+	--enable|-e)
+		ACTION="enable"
+		;;
+	--disable|-d)
+		ACTION="disable"
+		;;
+	--reset|-r)
+		ACTION="reset"
+		;;
+	--directory|-i)
+		DASD_STATISTICS_DIR="$2"
+		shift
+		if [[ ! -d $DASD_STATISTICS_DIR ]]
+		then
+			echo "$CMD: $DASD_STATISTICS_DIR is not a directory" >&2
+			exit 1
+		fi
+		;;
+	--long|-l)
+		OUTPUT="extended"
+		;;
+	--columns|-c)
+		NUMBER_COLUMNS="$2"
+		if [[ ! "$NUMBER_COLUMNS" -gt 0 ]]
+		then
+			echo "$CMD: $NUMBER_COLUMNS is not a positive integer number" >&2
+			exit 1
+		fi
+		shift
+		;;
+	--column-width|-w)
+		COLUMN_WIDTH="$2"
+		if [[ ! "$COLUMN_WIDTH" -gt 0 ]]
+		then
+			echo "$CMD: $COLUMN_WIDTH is not a positive integer number" >&2
+			exit 1
+		fi
+		shift
+		;;
+	--verbose|-V)
+		VERBOSE=true
+		;;
+	--version|-v)
+		print_version
+		exit 0
+		;;
+	-*)
+		echo "$CMD: Invalid option $1" >&2
+		echo "Try '$CMD --help' for more information." >&2
+		exit 1
+		;;
+	*)
+		ALLFILES="$ALLFILES $1"
+		;;
+	esac
+	shift
+done
+
+# if no directory is given on command line, find dasd directory in debugfs
+if [[ "$DASD_STATISTICS_DIR" == "" ]]
+then
+	while read -a mntentries
+	do
+		if [[ "${mntentries[2]}" == "debugfs" ]]
+		then
+			DASD_STATISTICS_DIR="${mntentries[1]}"
+			verbose_msg "found debugfs mount point $DASD_STATISTICS_DIR"
+			break;
+		fi
+	done < /etc/mtab
+	if [[ "$DASD_STATISTICS_DIR" == "" ]]
+	then
+		echo "$CMD: No debugfs mount point found" >&2
+		exit 1
+	fi
+	DASD_STATISTICS_DIR="$DASD_STATISTICS_DIR/dasd"
+	if [[ ! -d "$DASD_STATISTICS_DIR" ]]
+	then
+		echo "$CMD: Default DASD debugfs directory $DASD_STATISTICS_DIR does not exist" >&2
+		exit 1
+	fi
+fi
+
+# look for directories that contain statistics
+if [[ "$ALLFILES" == "" ]]
+then
+	ALLFILES=$(ls -x $DASD_STATISTICS_DIR)
+	explicitstats="false"
+else
+	explicitstats="true"
+fi
+ALLSTATS=""
+for f in $ALLFILES
+do
+	if [[ -f "$DASD_STATISTICS_DIR/$f/statistics" ]]
+	then
+		ALLSTATS="$ALLSTATS $f"
+	elif [[ $explicitstats == "true" ]]
+	then
+		echo "$CMD: No statistics found for $f" >&2
+	fi
+done
+
+verbose_msg "found the following statistics in directory $DASD_STATISTICS_DIR:"
+verbose_msg "$ALLSTATS"
+
+# execute the required operation
+for f in $ALLSTATS
+do
+	case $ACTION in
+	"enable")
+		enable_stat "$DASD_STATISTICS_DIR/$f/statistics"
+		;;
+	"disable")
+		disable_stat "$DASD_STATISTICS_DIR/$f/statistics"
+		;;
+	"reset")
+		reset_stat "$DASD_STATISTICS_DIR/$f/statistics"
+		;;
+	"print")
+		print_stat "$DASD_STATISTICS_DIR/$f/statistics"
+		;;
+	*)
+		echo "error"
+		exit 1
+		;;
+	esac
+done
+
+
--- /dev/null
+++ b/zconf/dasdstat.8
@@ -0,0 +1,97 @@
+.TH LSDASD 8 "Feb 2011" "s390-tools"
+
+.SH NAME
+dasdstat \- read or modify the statistics of the DASD device driver.
+
+.SH SYNOPSIS
+.TP 8
+.B dasdstat
+.RB [ -h ]
+.TP 8
+.B dasdstat
+.RB [ -e ]
+.RB [ -d ]
+.RB [ -r ]
+.RB [ -i ]
+.RB [ -l ]
+.RB [ -c ]
+.RB [ -w ]
+.RB [ -V ]
+.RB [ -v ]
+.RI [ <statistic> " [" <statistic> "] ...]]"
+
+.SH DESCRIPTION
+The dasdstat command provides easy access to the debugfs based
+statistics of the DASD device driver.
+
+The DASD statistics feature allows to gather statistical data for the
+I/O requests processed by the DASD device driver. This data can be
+collected for individual DASD CCW devices (including PAV base and
+alias devices), DASD block devices, or globally for all requests
+handled by the DASD device driver.
+
+When no other options are specified, then the default operation is to
+print the statistics data in a formatted table. When no specific list
+of statistics is given, then the operation will be performed on all
+available statistics.
+
+.SH OPTIONS
+.TP 8
+.BR -h | --help
+Print help text.
+.TP
+.BR -e | --enable
+Enable the statistics.
+.TP
+.BR -d | --disable
+Disable the statistics.
+.TP
+.BR -r | --reset
+Reset the statistics.
+.TP
+.BR -i | --directory
+Specify the directory in which the statistics can be found.
+.TP
+.BR -l | --long
+Print more detailed information, e.g differentiate between read and
+write requests.
+.TP
+.BR -c | --columns " \fI<number>\fR"
+Format the output in a table with the given number of columns.
+.TP
+.BR -w | --column-width " \fI<width>\fR"
+Set the minimum width of the columns in the output table.
+.TP
+.BR -V | --verbose
+Print more verbose information.
+.TP
+.BR -v | --version
+Print the version of the s390-tools package and the command.
+.TP
+\fB<statistic>\fR =
+Name of a statistic that the command should work on.
+
+.SH EXAMPLES
+\fBdasdstat\fR
+.RS
+Print a statistics table for each enabled statistic.
+.RE
+
+\fBdasdstat -e\fR
+.RS
+Enable all DASD statistics.
+.RE
+
+\fBdasdstat -l dasda 0.0.1800 0.0.18fe 0.0.18ff\fR
+.RS
+Print a detailed statistics table for DASD block device dasda and CCW
+devices 0.0.1800, 0.0.18fe and 0.0.18ff. A typical scenario for this
+example would be that dasda is the block device that belongs to
+PAV base device 0.0.1800, and CCW devices 0.0.18fe and 0.0.18ff are the
+associated alias devices.
+.RE
+
+.SH AUTHOR
+.nf
+This man-page was written by Stefan Weinhuber <wein@de.ibm.com>.
+.fi
--- a/zconf/Makefile
+++ b/zconf/Makefile
@@ -4,11 +4,11 @@
 include ../common.mak
 
 SCRIPTS	= lsdasd lstape lscss chccwdev lsqeth lszfcp lschp chchp lszcrypt \
-	  chzcrypt cio_ignore znetconf
+	  chzcrypt cio_ignore znetconf dasdstat
 USRSBIN_SCRIPTS = lsmem chmem lsluns
 MANPAGES= lsdasd.8 lstape.8 lscss.8 chccwdev.8 lsqeth.8 lszfcp.8 lschp.8 \
 	  chchp.8 lszcrypt.8 chzcrypt.8 lsluns.8 cio_ignore.8 znetconf.8 \
-	  chmem.8 lsmem.8
+	  chmem.8 lsmem.8 dasdstat.8
 
 all:
 
