Files
s390-tools/zconf/lszfcp
Steffen Maier bdaef6e76d lszfcp: associative array speeds up extended SCSI device list a lot
The linear search in the nested loop over SCSI devices and over the indexed
array is still time consuming. So replace the indexed array with an
associative array. Build the array once outside of the nested loop by using
pathname expansion and extracting key and value from each item. Within
the SCSI device loop, an array entry is removed by means of the key without
linear search.

This commit is separate from the previous one so it is possible to revert
this one in case associative arrays would not be available and still get
the speed improvement from the indexed array in the previous commit.

Before:

$ time ~/git/s390-tools/zconf/lszfcp -De | wc -l
1036

real	0m17.605s
user	0m17.159s
sys	0m0.308s

After:

$ time ~/git/s390-tools/zconf/lszfcp -De | wc -l
1036

real	0m0.207s
user	0m0.175s
sys	0m0.032s

Reviewed-by: Benjamin Block <bblock@linux.ibm.com>
Signed-off-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2024-11-22 17:18:37 +01:00

606 lines
15 KiB
Bash
Executable File

#!/bin/bash
#
# lszfcp - Tool to display information about zfcp devices (adapters/ports/units)
#
# Copyright IBM Corp. 2006, 2023
#
# s390-tools is free software; you can redistribute it and/or modify
# it under the terms of the MIT license. See LICENSE for details.
#
SCRIPTNAME='lszfcp'
SYSFS=`cat /proc/mounts | grep -m1 sysfs | awk '{ print $2 }'`
FC_CLASS=false
# Command line parameters
VERBOSITY=0
SHOW_MODPARMS=0
SHOW_HOSTS=0
SHOW_PORTS=0
SHOW_DEVICES=0
SHOW_ATTRIBUTES=false
SHOW_MORE_ATTRS=0
SHOW_EXTENDED=false
unset PAR_BUSID PAR_WWPN PAR_LUN
##############################################################################
check_sysfs()
{
if [ -z $SYSFS -o ! -d $SYSFS -o ! -r $SYSFS ]; then
echo "Error: sysfs not available."
exit 1
fi
}
check_zfcp_support()
{
if [ ! -e $SYSFS/bus/ccw/drivers/zfcp ]; then
echo "Error: No zfcp support available."
echo "Load the zfcp module or compile"
echo "the kernel with zfcp support."
exit 1
fi
}
check_fcp_devs()
{
local found=0
if $FC_CLASS; then
# theoretically this includes "defunct" (and thus online) devs
ignore=`ls $SYSFS/class/fc_host/host* 2>&1`
else
ignore=`ls $SYSFS/devices/css0/[0-9]*/[0-9]*/host[0-9]* 2>&1`
fi
[ $? -eq 0 ] && found=1
if [ $found -eq 0 ] && $SHOW_EXTENDED; then
# nothing found yet so search for "defunct" FCP devices
ignore=$(ls -dX $SYSFS/devices/css[0-9]*/defunct/[0-9]*/host[0-9]* 2>&1)
[ $? -eq 0 ] && found=1
fi
if [ $found -eq 0 ] && $SHOW_EXTENDED; then
local mypath mylist=""
# nothing found yet so search for FCP devices never been online
for mypath in $SYSFS/bus/ccw/drivers/zfcp/*.*.*; do
if [ "$mypath" = "$SYSFS/bus/ccw/drivers/zfcp/*.*.*" ];
then
break # glob did not match anything
fi
[ -d $mypath/host[0-9]* ] && continue # is/was online
mylist="$mylist $(readlink -e $mypath)/-"
done
[ -n "$mylist" ] && found=1
fi
if [ $found -eq 0 ]; then
echo "Error: No fcp devices found."
exit 1
fi
}
check_fc_class()
{
if [ -d "$SYSFS/class/fc_host" ]; then
FC_CLASS=true
fi
}
print_version()
{
cat <<EOF
$SCRIPTNAME: version %S390_TOOLS_VERSION%
Copyright IBM Corp. 2006, 2019
EOF
}
print_help()
{
cat <<EOF
Usage: $SCRIPTNAME [OPTIONS]
Provide information contained in sysfs about zfcp adapters, ports and
units that are online.
Mandatory arguments to long options are mandatory for short options too.
OPTIONS:
-H, --hosts show host information (default)
-P, --ports show remote port information
-D, --devices show SCSI device information
-Z, --modparms show zfcp (module) parameters
-b, --busid=BUSID select specific busid
-p, --wwpn=WWPN select specific port name
-l, --lun=LUN select specific LUN
-a, --attributes show attributes
-m, --moreattrs show more attributes
(host: css; port: zfcp_port; SCSI device: zfcp_unit)
specify twice to show more attributes for SCSI devices
(scsi_disk,block,integrity,queue,iosched)
-V, --verbose show sysfs paths of associated class
and bus devices
-e, --extended extended output format
-s, --sysfs=PATH use path as sysfs (for dbginfo archives)
-h, --help print this help
-v, --version print version information
EXAMPLE:
List for all zfcp adapters, ports and units the names of their
associated SCSI hosts, FC remote ports and SCSI devices.
#> lszfcp -P -H -D
0.0.3d0c host0
0.0.3d0c/0x500507630300c562 rport-0:0-0
0.0.3d0c/0x500507630300c562/0x4010403300000000 0:0:0:0
The default is to list bus_ids of all zfcp adapters and corresponding
SCSI host names (equals "lszfcp -H").
EOF
}
show_attributes()
{
if [ -z $1 -o ! -d $1 -o ! -r $1 ]; then
return 1
fi
for FILE in `ls $1`; do
read 2>/dev/null CONTENT < $1/$FILE
# Read fails for directories and
# files with permissions 0200.
if [ $? -ne 0 ]; then
continue
fi
printf " %-19s = \"%s\"\n" "$FILE" "$CONTENT"
done
}
show_modparms()
{
local modparmdir=$SYSFS/module/zfcp/parameters
[ -d "$modparmdir" ] || return
if [ $VERBOSITY -ne 0 ]; then
echo "$modparmdir"
fi
echo 'Module = "zfcp"'
show_attributes "$modparmdir"
echo
}
show_hosts()
{
HOST_LIST=`ls -dX $SYSFS/devices/css0/[0-9]*/[0-9]*/host[0-9]* \
2> /dev/null`
if $SHOW_EXTENDED; then
# add all "defunct" FCP devices
HOST_LIST="$HOST_LIST
$(ls -dX $SYSFS/devices/css[0-9]*/defunct/[0-9]*/host[0-9]* 2>/dev/null)"
# add all FCP devices that have never been online
for HOST_PATH in $SYSFS/bus/ccw/drivers/zfcp/*.*.*; do
[ -d $HOST_PATH/host[0-9]* ] && continue # is/was online
HOST_LIST="$HOST_LIST
$(readlink -e $HOST_PATH)/-"
done
fi
for HOST_PATH in $HOST_LIST; do
SCSI_HOST=`basename $HOST_PATH`
ADAPTER_PATH=`dirname $HOST_PATH`
ADAPTER=`basename $ADAPTER_PATH`
[ $ADAPTER != ${PAR_BUSID:-$ADAPTER} ] && continue
read ONLINE < $ADAPTER_PATH/online
read AVAILABILITY < $ADAPTER_PATH/availability
[ -r "$SYSFS/class/fc_host/$SCSI_HOST/port_state" ] && read PORT_STATE < "$SYSFS/class/fc_host/$SCSI_HOST/port_state"
[ -r $ADAPTER_PATH/failed ] && read FAILED < $ADAPTER_PATH/failed
DEFUNCT=${ADAPTER_PATH%/*} # strip devbusid
DEFUNCT=${DEFUNCT##*/} # basename
HOSTMARKER=""
if $SHOW_EXTENDED; then
if [ "$DEFUNCT" = "defunct" ]; then
HOSTMARKER=" defunct"
elif [ "$ONLINE" = "0" ]; then
HOSTMARKER=" offline"
elif [ "$PORT_STATE" = "Linkdown" ]; then
HOSTMARKER=" linkdown"
elif [ "$FAILED" = "1" ]; then
HOSTMARKER=" failed"
elif [ "$AVAILABILITY" != "good" ]; then
HOSTMARKER=" NotAvailable"
elif [ "$PORT_STATE" != "Online" ]; then
HOSTMARKER=" NotOnline"
fi
fi
if [ $VERBOSITY -eq 0 ]; then
echo $ADAPTER $SCSI_HOST$HOSTMARKER
else
echo $ADAPTER_PATH$HOSTMARKER
if [ "$SCSI_HOST" != "-" ]; then
$FC_CLASS && echo "$SYSFS/class/fc_host/$SCSI_HOST"
echo "$SYSFS/class/scsi_host/$SCSI_HOST"
else
echo "-"
echo "-"
fi
fi
if $SHOW_ATTRIBUTES; then
if [ $SHOW_MORE_ATTRS -ge 1 ]; then
echo 'Bus = "css"'
if [ "$DEFUNCT" = "defunct" ]; then
printf " %-19s\n" "defunct"
else
show_attributes ${ADAPTER_PATH%/*}
fi
fi
echo 'Bus = "ccw"'
show_attributes $ADAPTER_PATH
if [ "$SCSI_HOST" = "-" ]; then
# skip output of non-existent fc_host & scsi_host
echo
continue
fi
if $FC_CLASS; then
echo 'Class = "fc_host"'
show_attributes \
"$SYSFS/class/fc_host/$SCSI_HOST"
fi
echo 'Class = "scsi_host"'
show_attributes "$SYSFS/class/scsi_host/$SCSI_HOST"
echo
fi
done
}
show_ports()
{
# Without fc_remote_class there is far less information to display.
if ! $FC_CLASS; then
PORT_LIST=`ls -d $SYSFS/devices/css0/*/*/0x*`
for PORT_PATH in $PORT_LIST; do
WWPN=`basename $PORT_PATH`
ADAPTER=`basename \`dirname $PORT_PATH\``
[ $WWPN != ${PAR_WWPN:-$WWPN} ] && continue
[ $ADAPTER != ${PAR_BUSID:-$ADAPTER} ] && continue
if [ $VERBOSITY -eq 0 ]; then
echo "$ADAPTER/$WWPN"
else
echo $PORT_PATH
fi
done
return
fi
if [ -e $SYSFS/class/fc_remote_ports/ ]; then
PORT_LIST=`ls -d $SYSFS/class/fc_remote_ports/* 2>/dev/null`
fi;
for FC_PORT_PATH in $PORT_LIST; do
PORT=`basename $FC_PORT_PATH`
read PORT_STATE < $FC_PORT_PATH/port_state
if [ "$PORT_STATE" == "Online" ] || $SHOW_EXTENDED; then
read WWPN < $FC_PORT_PATH/port_name
else
continue
fi
PORTSTATEMARKER=""
$SHOW_EXTENDED && [ "$PORT_STATE" != "Online" ] \
&& PORTSTATEMARKER=" NotOnline"
[ $WWPN != ${PAR_WWPN:-$WWPN} ] && continue
local sysreal=$(readlink -e "$FC_PORT_PATH")
local ADAPTER=""
while [ -n "$sysreal" ]; do
# ascend to parent: strip last path part
sysreal=${sysreal%/*}
[ -h $sysreal/subsystem ] || continue
local subsystem=$(readlink -e $sysreal/subsystem)
if [ "${subsystem##*/}" = "ccw" ]; then
ADAPTER=${sysreal##*/}
break
fi
done
[ -z "$ADAPTER" ] && continue # skip not zfcp-attached ones
if [ -d $SYSFS/devices/css[0-9]*/[0-9d]*/[0-9]*/$WWPN/../host[0-9]*/$PORT ];
then
ADAPTER_PORT_PATH=$SYSFS/devices/css[0-9]*/[0-9d]*/$ADAPTER/$WWPN
else
ADAPTER_PORT_PATH="-"
fi
[ $ADAPTER != ${PAR_BUSID:-$ADAPTER} ] && continue
if [ $VERBOSITY -eq 0 ]; then
echo "$ADAPTER/$WWPN $PORT$PORTSTATEMARKER"
else
if [ "$ADAPTER_PORT_PATH" != "-" ]; then
echo $ADAPTER_PORT_PATH
else
echo "- (NoMoreZfcpPort:$ADAPTER/$WWPN)"
fi
echo $FC_PORT_PATH$PORTSTATEMARKER
fi
if $SHOW_ATTRIBUTES; then
if [ $SHOW_MORE_ATTRS -ge 1 ] \
&& [ "$ADAPTER_PORT_PATH" != "-" ] \
&& [ -d $ADAPTER_PORT_PATH ];
then
# fc_rport can exist without zfcp_port
# (e.g. after port_remove)
echo 'Class = "zfcp_port"'
show_attributes "$ADAPTER_PORT_PATH"
fi
echo 'Class = "fc_remote_ports"'
show_attributes "$FC_PORT_PATH"
echo
fi
done
}
show_devices()
{
# Differentiate old and new sysfs layout
if $FC_CLASS; then
SCSI_DEVICE_LIST=`ls -d \
$SYSFS/bus/ccw/drivers/zfcp/*/host*/rport*/target*/*:*:*:* \
2>/dev/null`
else
SCSI_DEVICE_LIST=`ls -d $SYSFS/devices/css0/*/*/host[0-9]*/*/`
fi
declare -A ZFCP_UNIT_DICT
if $SHOW_EXTENDED; then
ZFCP_UNITS=false
for UNIT_PATH in "$SYSFS"/devices/css[0-9]*/[0-9d]*/[0-9]*/0x*/0x*; do
if [ "$UNIT_PATH" = "$SYSFS/devices/css[0-9]*/[0-9d]*/[0-9]*/0x*/0x*" ]; then
break # no match (as seen without nullglob)
else
ZFCP_UNITS=true
fi
STRIPPED_PATH=${UNIT_PATH}
L=${UNIT_PATH##*/}
STRIPPED_PATH=${STRIPPED_PATH%/*}
W=${STRIPPED_PATH##*/}
STRIPPED_PATH=${STRIPPED_PATH%/*}
A=${STRIPPED_PATH##*/}
ZFCP_UNIT_DICT["$A/$W/$L"]=${UNIT_PATH}
done
else
ZFCP_UNITS=false
fi
if [ -z "$SCSI_DEVICE_LIST" ] && ! "$ZFCP_UNITS"; then
if $SHOW_EXTENDED; then
echo "Error: No zfcp-attached SCSI devices found."
else
echo "Error: No fcp devices found."
fi
fi
for SCSI_DEVICE_PATH in $SCSI_DEVICE_LIST; do
read ADAPTER < $SCSI_DEVICE_PATH/hba_id
read WWPN < $SCSI_DEVICE_PATH/wwpn
read LUN < $SCSI_DEVICE_PATH/fcp_lun
ZFCP_UNIT_PATH="${ZFCP_UNIT_DICT[$ADAPTER/$WWPN/$LUN]}"
# remove from ZFCP_UNIT_DICT if SCSI device exists
unset "ZFCP_UNIT_DICT[$ADAPTER/$WWPN/$LUN]"
[ $LUN != ${PAR_LUN:-$LUN} ] && continue
[ $WWPN != ${PAR_WWPN:-$WWPN} ] && continue
[ $ADAPTER != ${PAR_BUSID:-$ADAPTER} ] && continue
SDEVMARKER=""
if $SHOW_EXTENDED; then
[ -n "$ZFCP_UNIT_PATH" ] || SDEVMARKER="$SDEVMARKER auto"
read SDEVSTATE < $SCSI_DEVICE_PATH/state
[ "$SDEVSTATE" != "running" ] \
&& SDEVMARKER="$SDEVMARKER NotRunning"
else
# build manually because ZFCP_UNIT_DICT is not used
ZFCP_UNIT_PATH=$SYSFS/devices/css[0-9]*/[0-9d]*/$ADAPTER/$WWPN/$LUN
fi
if [ $VERBOSITY -eq 0 ]; then
echo "$ADAPTER/$WWPN/$LUN ${SCSI_DEVICE_PATH##*/}$SDEVMARKER"
else
echo "`ls -d $SYSFS/devices/css0/[0-9d]*/$ADAPTER`/$WWPN/$LUN"
echo ${SCSI_DEVICE_PATH%*/}$SDEVMARKER # without trailing slash
# On live systems, there are links to the block and
# generic devices. In a dbginfo archive, these links
# are not present. Therefore, fall back to reading
# the runtime.out log file.
if [ `ls $SCSI_DEVICE_PATH | grep -c block:` -eq 1 ]
then
BLOCK_DEV=`ls $SCSI_DEVICE_PATH | grep block:`
GEN_DEV=`ls $SCSI_DEVICE_PATH |\
grep scsi_generic:`
echo -n "$SYSFS/block/${BLOCK_DEV#*:} "
echo "$SYSFS/class/scsi_generic/${GEN_DEV#*:}"
elif [ -d $SCSI_DEVICE_PATH/block ] && $SHOW_EXTENDED
then
# case without CONFIG_SYSFS_DEPRECATED
BLOCK_DEV=$(echo $SCSI_DEVICE_PATH/block/*)
BLOCK_DEV=${BLOCK_DEV##*/}
echo -n "$SYSFS/block/$BLOCK_DEV "
local SGPATH=$SCSI_DEVICE_PATH/scsi_generic
if [ -d $SGPATH ]; then
GEN_DEV=$(echo $SGPATH/*)
GEN_DEV=${GEN_DEV##*/}
echo "$SYSFS/class/scsi_generic/$GEN_DEV"
else
echo "-"
fi
# FIXME Find a way to assign the generic devices.
elif [ -r $SYSFS/../runtime.out ]; then
SCSI_DEV=`basename $SCSI_DEVICE_PATH`
echo "$SYSFS/block/"`grep -r "\[$SCSI_DEV\]"\
$SYSFS/../runtime.out |\
awk -F "/dev/" '{print $2}'`
fi
fi
if $SHOW_ATTRIBUTES && [ $SHOW_MORE_ATTRS -ge 1 ]; then
# auto scan LUNs not necessarily have a zfcp_unit
if [ -n "$ZFCP_UNIT_PATH" ] && [ -d $ZFCP_UNIT_PATH ]; then
echo 'Class = "zfcp_unit"'
show_attributes "$ZFCP_UNIT_PATH"
fi
fi
if $SHOW_ATTRIBUTES; then
echo 'Class = "scsi_device"'
show_attributes "$SCSI_DEVICE_PATH"
fi
if $SHOW_ATTRIBUTES && [ $SHOW_MORE_ATTRS -ge 2 ]; then
if [ -d $SCSI_DEVICE_PATH/scsi_disk ]; then
echo 'Class = "scsi_disk"'
show_attributes "$SCSI_DEVICE_PATH/scsi_disk/$(basename $SCSI_DEVICE_PATH)"
fi
if [ -d $SCSI_DEVICE_PATH/block ]; then
echo 'Class = "block"'
show_attributes "$SCSI_DEVICE_PATH/block/*/"
fi
if [ -d $SCSI_DEVICE_PATH/block/*/integrity ]; then
echo 'Class = "block_integrity"'
show_attributes "$SCSI_DEVICE_PATH/block/*/integrity"
fi
if [ -d $SCSI_DEVICE_PATH/block/*/queue ]; then
echo 'Class = "block_queue"'
show_attributes "$SCSI_DEVICE_PATH/block/*/queue"
fi
if [ -d $SCSI_DEVICE_PATH/block/*/queue/iosched ]; then
echo 'Class = "block_queue_iosched"'
show_attributes "$SCSI_DEVICE_PATH/block/*/queue/iosched"
fi
fi
if $SHOW_ATTRIBUTES; then
echo
fi
done
# what's left in ZFCP_UNIT_DICT are now units without SCSI device
for UNIT_PATH in "${ZFCP_UNIT_DICT[@]}"; do
STRIPPED_PATH=$UNIT_PATH
LUN=${UNIT_PATH##*/}
STRIPPED_PATH=${STRIPPED_PATH%/*}
WWPN=${STRIPPED_PATH##*/}
STRIPPED_PATH=${STRIPPED_PATH%/*}
ADAPTER=${STRIPPED_PATH##*/}
[ $LUN != ${PAR_LUN:-$LUN} ] && continue
[ $WWPN != ${PAR_WWPN:-$WWPN} ] && continue
[ $ADAPTER != ${PAR_BUSID:-$ADAPTER} ] && continue
if [ $VERBOSITY -eq 0 ]; then
echo "$ADAPTER/$WWPN/$LUN - failed"
else
echo "$UNIT_PATH failed"
echo "-"
echo "- -"
fi
if $SHOW_ATTRIBUTES && [ $SHOW_MORE_ATTRS -ge 1 ]; then
echo 'Class = "zfcp_unit"'
show_attributes "$UNIT_PATH"
fi
if $SHOW_ATTRIBUTES; then
echo
fi
done
}
##############################################################################
ARGS=`getopt --options ahvHPDVb:p:l:s:emZ --longoptions \
attributes,help,version,hosts,ports,devices,verbose,busid:,wwpn:,lun:,sysfs:,extended,moreattrs,modparms \
-n "$SCRIPTNAME" -- "$@"`
if [ $? -ne 0 ]; then
echo
print_help
exit
fi
eval set -- "$ARGS"
for ARG; do
case "$ARG" in
-a|--attributes) SHOW_ATTRIBUTES=true; shift 1;;
-m|--moreattrs) ((SHOW_MORE_ATTRS++)); shift 1;;
-e|--extended) SHOW_EXTENDED=true; shift 1;;
-b|--busid) PAR_BUSID=$2; shift 2;;
-h|--help) print_help; exit 0;;
-l|--lun) PAR_LUN=$2; shift 2;;
-p|--wwpn) PAR_WWPN=$2; shift 2;;
-v|--version) print_version; exit 0;;
-H|--hosts) SHOW_HOSTS=1; shift 1;;
-D|--devices) SHOW_DEVICES=1; shift 1;;
-P|--ports) SHOW_PORTS=1; shift 1;;
-V|--verbose) VERBOSITY=1; shift 1;;
-Z|--modparms) SHOW_MODPARMS=1; shift 1;;
-s|--sysfs) SYSFS=$2; shift 2;;
--) shift; break;;
esac
done
check_sysfs
check_zfcp_support
check_fc_class
check_fcp_devs
default=1
if [ $SHOW_MODPARMS -eq 1 ]; then
default=0; show_modparms
fi
if [ $SHOW_HOSTS -eq 1 ]; then
default=0; show_hosts
elif [ $SHOW_PORTS -eq 0 -a $SHOW_DEVICES -eq 0 -a -n "$PAR_BUSID" ]; then
default=0; show_hosts
fi
if [ $SHOW_PORTS -eq 1 ]; then
default=0; show_ports
elif [ $SHOW_HOSTS -eq 0 -a $SHOW_DEVICES -eq 0 -a -n "$PAR_WWPN" ]; then
default=0; show_ports
fi
if [ $SHOW_DEVICES -eq 1 ]; then
default=0; show_devices
elif [ $SHOW_HOSTS -eq 0 -a $SHOW_PORTS -eq 0 -a -n "$PAR_LUN" ]; then
default=0; show_devices
fi
if [ $default == 1 ]; then
show_hosts
fi