The branch main has been updated by dteske:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=60c7313074b93adf4ea70ff44b78b3b4fc15c29f

commit 60c7313074b93adf4ea70ff44b78b3b4fc15c29f
Author:     Devin Teske <[email protected]>
AuthorDate: 2026-09-06 22:34:34 +0000
Commit:     Devin Teske <[email protected]>
CommitDate: 2026-09-06 22:34:34 +0000

    Introduce nfsdtop(8)
    
    Display top-like NFS server I/O using dtrace(1).
    Also supports JSON output for time-series.
    
    Relnotes:       yes
    
    Reviewed by:    ziaee, bcr, adrian
    Differential Revision:  https://reviews.freebsd.org/D59438
---
 usr.sbin/Makefile          |    1 +
 usr.sbin/nfsdtop/Makefile  |    6 +
 usr.sbin/nfsdtop/nfsdtop   | 1565 ++++++++++++++++++++++++++++++++++++++++++++
 usr.sbin/nfsdtop/nfsdtop.8 |  310 +++++++++
 4 files changed, 1882 insertions(+)

diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 9e885d5d45c5..8336dbde4085 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -51,6 +51,7 @@ SUBDIR=       adduser \
        nfscbd \
        nfsd \
        nfsdumpstate \
+       nfsdtop \
        nfsrevoke \
        nfsuserd \
        nmtree \
diff --git a/usr.sbin/nfsdtop/Makefile b/usr.sbin/nfsdtop/Makefile
new file mode 100644
index 000000000000..b269ef4ea6d9
--- /dev/null
+++ b/usr.sbin/nfsdtop/Makefile
@@ -0,0 +1,6 @@
+PACKAGE=       nfs
+
+SCRIPTS=       nfsdtop
+MAN=           nfsdtop.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsdtop/nfsdtop b/usr.sbin/nfsdtop/nfsdtop
new file mode 100755
index 000000000000..1bd03ea6d017
--- /dev/null
+++ b/usr.sbin/nfsdtop/nfsdtop
@@ -0,0 +1,1565 @@
+#!/bin/sh
+############################################################ LICENSE
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2020 Devin Teske
+#
+############################################################ IDENT(1)
+#
+# $Title: Script to generate top-like statistics for nfsd I/O $
+#
+############################################################ INFORMATION
+#
+# In nfsdtop, a ``view'' is the user's choice between -c, -g, -s, or -u.
+# For example, `-u' asks nfsdtop to display the ``user view'' where statistics
+# displayed are on a per-user basis.
+#
+# The code is broken down into:
+#      - View selection (user choice)
+#      - Filter selection (user choice)
+#      - dtrace execution
+#      - awk to process dtrace output
+#
+# Code navigating Search Terms/ST:
+#      DTRACE          Start of dtrace
+#      FILTERS         Data filters
+#      JSONDATA        JSON data generation
+#      OPS             Data operations (read vs write)
+#      POST            dtrace post-processor (awk)
+#      PRE             Start of pre-processor (sh)
+#      SORTING         Sort routines
+#      TRACEDATA       dtrace data generation and processing
+#      VIEWDATA        View data generation
+#      VIEWS           View processing
+#
+############################################################ DEFAULTS
+
+DEFAULT_INTERVAL=2.0 # seconds
+
+#
+# User/Group map files
+#
+DEFAULT_PASSWD_MAP=.nfsd.passwd
+DEFAULT_GROUP_MAP=.nfsd.group
+
+#
+# Network IP map file
+#
+DEFAULT_IP_MAP=.nfsd.hosts
+
+############################################################ GLOBALS
+
+VERSION='$Version: 6.1.3 $'
+
+pgm="${0##*/}" # Program basename
+
+#
+# Global exit status
+#
+SUCCESS=0
+FAILURE=1
+
+#
+# Command-line options
+#
+COLOR=1                                        # -a
+DEBUG=                                 # -d
+DEBUGGER=                              # -D
+FILTER_CLIENT=                         # -C ip
+FILTER_GROUP=                          # -G group
+FILTER_USER=                           # -U user
+GROUP_MAP="$DEFAULT_GROUP_MAP"         # -P file
+INTERVAL=$DEFAULT_INTERVAL             # -i sec
+IP_MAP="$DEFAULT_IP_MAP"               # -I file
+NO_NAMES=                              # -n
+NSAMPLES=                              # -N num
+OUTPUT_JSON=                           # -j
+PASSWD_MAP="$DEFAULT_PASSWD_MAP"       # -p file
+QUIET=                                 # -q
+RAW_VIEW=                              # -r
+REDACT=${NFSDTOP_REDACT:+1}            # -R
+SHOW_BYTES=                            # -b
+VIEW_CLIENT=                           # -c
+VIEW_GROUP=                            # -g
+VIEW_USER=                             # -u (default)
+WIDE_VIEW=                             # -w
+
+#
+# Miscellaneous
+#
+CONS=1
+[ -t 1 ] || CONS= COLOR= # stdout is not a tty
+_FILTER_CLIENT=
+_FILTER_GROUP=
+_FILTER_USER=
+INTERVAL_PROBE= # Calculated
+INTERVAL_SECONDS= # Raw value for awk
+VIEW=
+
+############################################################ FUNCTIONS
+
+die()
+{
+       local fmt="$1"
+       if [ "$fmt" ]; then
+               shift 1 # fmt
+               printf "%s: $fmt\n" "$pgm" "$@" >&2
+       fi
+       exit $FAILURE
+}
+
+usage()
+{
+       local fmt="$1"
+       local optfmt="\t%-11s %s\n"
+
+       exec >&2
+       if [ "$fmt" ]; then
+               shift 1 # fmt
+               printf "%s: $fmt\n" "$pgm" "$@"
+       fi
+
+       printf "Usage: %s [OPTIONS]\n" "$pgm"
+       printf "Options:\n"
+       printf "$optfmt" "-a" "Always enable color."
+       printf "$optfmt" "-b" "Show bytes instead of bandwidth."
+       printf "$optfmt" "-C ip" "Client filter (IPv4 only)."
+       printf "$optfmt" "-c" "View read/write activity by client."
+       printf "$optfmt" "-D" "Enable debugger."
+       printf "$optfmt" "-d" "Debug. Print dtrace script and exit."
+       printf "$optfmt" "-G group" "Group filter (name or id)."
+       printf "$optfmt" "-g" "View read/write activity by group."
+       printf "$optfmt" "-h" "Print usage statement and exit."
+       printf "$optfmt" "-I file" "IP map file. Default \`$DEFAULT_IP_MAP'."
+       printf "$optfmt" "-i sec" \
+               "Set interval seconds. Default \`$DEFAULT_INTERVAL'."
+       printf "$optfmt" "-J" "Output most JSON data. Same as \`-jcgu'."
+       printf "$optfmt" "-j" "Output JSON formatted data."
+       printf "$optfmt" "-N num" "Perform num samples and exit."
+       printf "$optfmt" "-n" "Do not attempt to map uid/gid/ip to names."
+       printf "$optfmt" "-o" "Force non-console output."
+       printf "$optfmt" "-P file" \
+               "Group map file. Default \`$DEFAULT_GROUP_MAP'."
+       printf "$optfmt" "-p file" \
+               "User map file. Default \`$DEFAULT_PASSWD_MAP'."
+       printf "$optfmt" "-q" "Quiet. Hide informational messages."
+       printf "$optfmt" "-R" "Redact potentially sensitive information."
+       printf "$optfmt" "-r" "Raw view. Do not format output of dtrace."
+       printf "$optfmt" "-U user" "User filter (name or id)."
+       printf "$optfmt" "-u" "View read/write activity by user (default)."
+       printf "$optfmt" "-v" "Print version and exit."
+       printf "$optfmt" "-w" "Wide view. Maximize width of first column."
+
+       die
+}
+
+run_dtrace()
+{
+       if [ "$DEBUG" ]; then
+               cat
+               return
+       fi
+
+       dtrace -s /dev/stdin "$@"
+}
+
+isint()
+{
+       local arg="${1#-}"
+       [ "${arg:-x}" = "${arg%[!0-9]*}" ]
+}
+
+isip()
+{
+        local IFS=. noctets=0 octet
+        for octet in $1; do
+                [ "$octet" ] || return 2
+                isint "$octet" || return 3
+                [ $octet -ge 0 ] || return 4
+                [ $octet -gt 255 ] && return 5
+                noctets=$(( $noctets + 1 ))
+        done
+        [ $noctets -eq 4 ]
+}
+
+checkip()
+{
+       isip "$1"
+       case $? in
+       1) die "-C argument \`%s' too many dots" "$1" ;;
+       2) die "-C argument \`%s' missing octet" "$1" ;;
+       3) die "-C argument \`%s' bad octet" "$1" ;;
+       4) die "-C argument \`%s' negative octet" "$1" ;;
+       5) die "-C argument \`%s' too big octet" "$1" ;;
+       esac
+}
+
+#
+# ST: CALLS
+#
+
+send_user()
+{
+       local type="$1"
+       shift 1 # type
+       printf "%s|%s\n" "$type" "$*"
+}
+
+info() { send_user info "$*"; }
+
+resize()
+{
+       local size
+       if [ -e /dev/tty ]; then
+               size=$( { stty size < /dev/tty; } 2> /dev/null )
+       else
+               size=$( stty size 2> /dev/null )
+       fi
+       send_user resize "${size:-24 80}"
+}
+
+############################################################ MAIN
+
+#
+# Process command-line options
+#
+while getopts abC:cDdG:ghI:i:JjN:noP:p:qRrU:uvw flag; do
+       case "$flag" in
+       a) COLOR=1 ;;
+       b) SHOW_BYTES=1 ;;
+       C) FILTER_CLIENT="$OPTARG" ;;
+       c) VIEW=CLIENT VIEW_CLIENT=1 ;;
+       D) DEBUGGER=1 RAW_VIEW=1 ;;
+       d) DEBUG=1 RAW_VIEW=1 ;;
+       G) FILTER_GROUP="$OPTARG" ;;
+       g) VIEW=GROUP VIEW_GROUP=1 ;;
+       I) IP_MAP="$OPTARG" ;;
+       i) INTERVAL="$OPTARG" ;;
+       J) VIEW=JSON OUTPUT_JSON=1 \
+               VIEW_CLIENT=1 VIEW_GROUP=1 VIEW_USER=1 ;;
+       j) OUTPUT_JSON=1 ;;
+       N) [ "$OPTARG" ] || usage "-N option requires an argument" # NOTREACHED
+               NSAMPLES="$OPTARG" ;;
+       n) NO_NAMES=1 ;;
+       o) CONS= COLOR= ;;
+       P) GROUP_MAP="$OPTARG" ;;
+       p) PASSWD_MAP="$OPTARG" ;;
+       q) QUIET=1 ;;
+       R) REDACT=1 ;;
+       r) RAW_VIEW=1 ;;
+       U) FILTER_USER="$OPTARG" ;;
+       u) VIEW=USER VIEW_USER=1 ;;
+       v) VERSION="${VERSION#*: }"
+               echo "${VERSION% $}"
+               exit $SUCCESS ;;
+       w) WIDE_VIEW=1 ;;
+       *) usage # NOTREACHED
+       esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Process command-line arguments
+#
+[ $# -eq 0 ] || usage "Too many arguments" # NOTREACHED
+
+#
+# Prevent non-functional option combinations
+#
+if [ "$SHOW_BYTES" ]; then
+       [ "$VIEW" != "JSON" ] || die "-b cannot be combined with -J"
+       [ ! "$OUTPUT_JSON" ] || die "-b cannot be combined with -j"
+fi
+
+#
+# Silently ignore previous view options unless JSON output
+#
+[ "$VIEW" ] || VIEW=USER VIEW_USER=1
+if [ ! "$OUTPUT_JSON" ]; then
+       case "$VIEW" in # ST: VIEWS
+       CLIENT) VIEW_GROUP= VIEW_USER= ;;
+       GROUP) VIEW_CLIENT= VIEW_USER= ;;
+       USER) VIEW_CLIENT= VIEW_GROUP= ;;
+       esac
+fi
+
+#
+# Validate `-i sec' option
+#
+case "$INTERVAL" in
+"") usage "missing -i argument" ;; # NOTREACHED
+0) die "-i sec must be non-zero" ;;
+*[!0-9.]*|*.*.*|.) die "-i sec must be a number" ;;
+*.*)
+       INTERVAL_SECONDS=$INTERVAL
+       ms=$( echo "$INTERVAL * 1000" | bc )
+       ms="${ms%%.*}"
+
+       #
+       # If, after multiplying by 1000 to convert sec to msec,
+       # the leading [non-decimal] digit is either missing or zero,
+       # the input was too small to produce timing of at least 1 msec
+       #
+       case "$ms" in
+       ""|0) die "-i sec must be at least 0.001" ;;
+       esac
+
+       INTERVAL_PROBE=profile:::tick-${ms}ms
+       ;;
+*)
+       INTERVAL_SECONDS=$INTERVAL
+       INTERVAL_PROBE=profile:::tick-${INTERVAL_SECONDS}s
+esac
+
+#
+# Validate `-N num' option
+#
+case "$NSAMPLES" in
+0) die "-N num must be non-zero" ;;
+*[!0-9]*) die "-N num must be a positive integer" ;;
+esac
+
+#
+# Process `-G group'/`-U user' option
+#
+case "$FILTER_GROUP" in
+"") : leave-empty ;;
+*[!0-9]*) # Translate from name to GID
+       _FILTER_GROUP=$( awk \
+               -v sq="'" \
+               -v group_map="$GROUP_MAP" \
+               -v name="$FILTER_GROUP" \
+       'BEGIN {
+               delete name2gid
+               while (getline < group_map > 0) {
+                       n = split($0, fields, /:/)
+                       name2gid[fields[1]] = fields[3]
+               }
+               close(group_map)
+               if (name in name2gid) {
+                       print name2gid[name]
+                       exit 0
+               }
+               gsub(sq, "&\\\\&&", name)
+               cmd = sprintf("getent group %s%s%s", sq, name, sq)
+               cmd | getline group
+               close(cmd)
+               if (split(group, fields, /:/) >= 3) {
+                       print fields[3]
+                       exit 0
+               }
+               exit 1
+       }' 2> /dev/null ) || die "Unknown group %s" "$FILTER_GROUP"
+       FILTER_GROUP="$_FILTER_GROUP"
+       ;;
+esac
+case "$FILTER_USER" in
+"") : leave-empty ;;
+*[!0-9]*) # Translate from name to UID
+       _FILTER_USER=$( awk \
+               -v sq="'" \
+               -v user_map="$PASSWD_MAP" \
+               -v name="$FILTER_USER" \
+       'BEGIN {
+               delete uid2name
+               while (getline < user_map > 0) {
+                       n = split($0, fields, /:/)
+                       name2uid[fields[1]] = fields[3]
+               }
+               close(user_map)
+               if (name in name2uid) {
+                       print name2uid[name]
+                       exit 0
+               }
+               gsub(sq, "&\\\\&&", name)
+               cmd = sprintf("getent passwd %s%s%s", sq, name, sq)
+               cmd | getline passwd
+               close(cmd)
+               if (split(passwd, fields, /:/) >= 3) {
+                       print fields[3]
+                       exit 0
+               }
+               exit 1
+       }' ) || die "Unknown user %s" "$FILTER_USER"
+       FILTER_USER="$_FILTER_USER"
+       ;;
+esac
+
+#
+# Process `-C ip' option
+#
+case "$FILTER_CLIENT" in
+"") : ok ;;
+*[a-zA-Z]*)
+       _FILTER_CLIENT=$( awk \
+               -v sq="'" \
+               -v ip_map="$IP_MAP" \
+               -v host="$FILTER_CLIENT" \
+       'BEGIN {
+               delete host2ip
+               while (getline < ip_map > 0) {
+                       if (/^[[:space:]]*(#|$)/) continue
+                       n = split($0, fields, /[[:space:]]+/)
+                       host2ip[fields[2]] = fields[1]
+               }
+               close(ip_map)
+               if (host in host2ip) {
+                       print host2ip[host]
+                       exit 0
+               } else if (host !~ /\.$/ && (host ".") in host2ip) {
+                       print host2ip[host "."]
+                       exit 0
+               }
+               gsub(sq, "&\\\\&&", host)
+               cmd = sprintf("host -t A %s%s%s", sq, host, sq)
+               cmd | getline
+               close(cmd)
+               if ($NF !~ /NXDOMAIN/) {
+                       print $NF
+                       exit 0
+               }
+               exit 1
+       }' 2> /dev/null ) || die "Unknown host %s" "$FILTER_CLIENT"
+       FILTER_CLIENT="$_FILTER_CLIENT"
+       checkip "$FILTER_CLIENT" # NOTREACHED if non-ip
+       ;;
+*)
+       checkip "$FILTER_CLIENT" # NOTREACHED if non-ip
+       ;;
+esac
+
+#
+# Get terminal size
+#
+size=$( resize )
+size="${size#*|}"
+if [ "$size" ]; then
+       cols="${size#*[$IFS]}"
+       rows="${size%%[$IFS]*}"
+fi
+case "$rows$cols" in
+""|*[!0-9]*)
+       cols=80
+       rows=24
+       ;;
+esac
+
+#
+# Run script
+# ST: PRE
+#
+{
+       trap resize WINCH # ST: SIGWINCH
+
+       #
+       # Start background dtrace
+       # NB: M-x package-install [RET] dtrace-script-mode [RET]
+       # ST: DTRACE
+       #
+       run_dtrace <<EOF &
+       ${DEBUG:+#!/usr/sbin/dtrace -s}
+       /* -*- mode: dtrace-script; tab-width: 4 -*- ;; Emacs
+        * vi: set ft=dtrace noet ts=4 sw=4 :: Vi/ViM
+        */
+       /**************************** PRAGMAS ****************************/
+
+       #pragma D option quiet
+
+       /***************************** TYPES *****************************/
+
+       typedef struct sainfo {
+               string addr;
+       } sainfo_t;
+
+       /**************************** GLOBALS ****************************/
+
+       inline int sa_data_size = 14;
+       inline char *sa_dummy_data = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+       this sainfo_t   sainfo;
+
+       /************************* MAP FUNCTIONS *************************/
+
+       inline string sa_data_addr[sa_family_t af, char data[sa_data_size]] =
+               af == AF_INET ? strjoin(
+                       strjoin(strjoin(lltostr(data[2] & 0xFF), "."),
+                               strjoin(lltostr(data[3] & 0xFF), ".")
+                       ),
+                       strjoin(strjoin(lltostr(data[4] & 0xFF), "."),
+                               lltostr(data[5] & 0xFF))
+               ) : "";
+
+       /* ST: OPS */
+
+       inline string opfunc[string func] =
+               func == "nfsrvd_read" ?         "read" :
+               func == "nfsrvd_write" ?        "write" :
+               "";
+
+       /************************** TRANSLATORS **************************/
+
+       translator sainfo_t < struct sockaddr *SA > {
+               addr = SA == NULL ?
+                       sa_data_addr[0, sa_dummy_data] :
+                       sa_data_addr[SA->sa_family, SA->sa_data];
+       };
+
+       /***************************** BEGIN *****************************/
+
+       BEGIN
+       {
+               printf("time|%d\n", walltimestamp / 1000000000);
+               printf("init|"); /* Initialize post-processor */
+               printf("===\n"); /* Clear screen and draw header */
+               printf("info|Sampling data for ${INTERVAL}s (\`-i sec')...\n");
+       }
+
+       /************************** READ PROBES **************************/
+
+       fbt:kernel:nfsrvd_read:entry
+       {
+               this->nfsd_read_nd = (struct nfsrv_descript *)args[0];
+               this->nfsd_read_cred = this->nfsd_read_nd == NULL ? NULL :
+                       this->nfsd_read_nd->nd_cred;
+               this->nfsd_read_nam = this->nfsd_read_nd == NULL ? NULL :
+                       this->nfsd_read_nd->nd_nam;
+               this->nfsd_read_info = xlate <sainfo_t>
+                       ((struct sockaddr *)this->nfsd_read_nam);
+       }
+
+       fbt:kernel:nfsvno_read:entry
+       /this->nfsd_read_nd != NULL/
+       {
+               this->uid = this->nfsd_read_cred->cr_ruid;
+               this->gid = this->nfsd_read_cred->cr_rgid;
+               this->bytes = args[2];
+
+               this->client_ip4 = this->nfsd_read_info.addr;
+
+               /* ST: FILTERS */
+
+               this->nfsd_read_unfiltered = ${FILTER_CLIENT:+
+                       this->client_ip4 == "$FILTER_CLIENT" ?
+                       }${FILTER_GROUP:+
+                       this->gid == $FILTER_GROUP ?
+                       }${FILTER_USER:+
+                       this->uid == $FILTER_USER ?
+                       }
+                       1${FILTER_CLIENT:+
+                       : 0}${FILTER_GROUP:+
+                       : 0}${FILTER_USER:+
+                       : 0};
+       }
+
+       fbt:kernel:nfsvno_read:entry
+       /this->nfsd_read_nd != NULL && this->nfsd_read_unfiltered/
+       {
+               /* ST: VIEWS TRACEDATA */
+${VIEW_CLIENT:+
+               @rd_client["@rd_client", this->client_ip4] =
+                       sum(this->bytes);
+}
+
+${VIEW_GROUP:+
+               @rd_group["@rd_group", this->gid] = sum(this->bytes);
+}
+
+${VIEW_USER:+
+               @rd_user["@rd_user", this->uid] = sum(this->bytes);
+}
+       }
+
+       /************************* WRITE  PROBES *************************/
+
+       fbt:kernel:nfsrvd_write:entry
+       {
+               this->nfsd_write_nd = (struct nfsrv_descript *)args[0];
+               this->nfsd_write_cred = this->nfsd_write_nd == NULL ? NULL :
+                       this->nfsd_write_nd->nd_cred;
+               this->nfsd_write_nam = this->nfsd_write_nd == NULL ? NULL :
+                       this->nfsd_write_nd->nd_nam;
+               this->nfsd_write_info = xlate <sainfo_t>
+                       ((struct sockaddr *)this->nfsd_write_nam);
+       }
+
+       fbt:kernel:nfsvno_write:entry
+       /this->nfsd_write_nd != NULL/
+       {
+               this->uid = this->nfsd_write_cred->cr_ruid;
+               this->gid = this->nfsd_write_cred->cr_rgid;
+               this->bytes = args[2];
+
+               this->client_ip4 = this->nfsd_write_info.addr;
+
+               this->filtered = 0;
+
+               /* ST: FILTERS */
+
+               this->nfsd_write_unfiltered = ${FILTER_CLIENT:+
+                       this->client_ip4 == "$FILTER_CLIENT" ?
+                       }${FILTER_GROUP:+
+                       this->gid == $FILTER_GROUP ?
+                       }${FILTER_USER:+
+                       this->uid == $FILTER_USER ?
+                       }
+                       1${FILTER_CLIENT:+
+                       : 0}${FILTER_GROUP:+
+                       : 0}${FILTER_USER:+
+                       : 0};
+       }
+
+       fbt:kernel:nfsvno_write:entry
+       /this->nfsd_write_nd != NULL && this->nfsd_write_unfiltered/
+       {
+               /* ST: VIEWS TRACEDATA */
+${VIEW_CLIENT:+
+               @wr_client["@wr_client", this->client_ip4] =
+                       sum(this->bytes);
+}
+
+${VIEW_GROUP:+
+               @wr_group["@wr_group", this->gid] = sum(this->bytes);
+}
+
+${VIEW_USER:+
+               @wr_user["@wr_user", this->uid] = sum(this->bytes);
+}
+       }
+
+       /************************ INTERVAL  PROBE ************************/
+
+       $INTERVAL_PROBE
+       {
+               printf("time|%d\n", walltimestamp / 1000000000);
+               printf("===\n");
+
+${VIEW_CLIENT:+
+               printa(@rd_client);
+               printa(@wr_client);
+               trunc(@rd_client);
+               trunc(@wr_client);
+}
+${VIEW_GROUP:+
+               printa(@rd_group);
+               printa(@wr_group);
+               trunc(@rd_group);
+               trunc(@wr_group);
+}
+${VIEW_USER:+
+               printa(@rd_user);
+               printa(@wr_user);
+               trunc(@rd_user);
+               trunc(@wr_user);
+}
+
+               printf("---\n");
+       }
+
+       /****************************** END ******************************/
+
+       END
+       {
+${VIEW_CLIENT:+
+               trunc(@rd_client);
+               trunc(@wr_client);
+}
+${VIEW_GROUP:+
+               trunc(@rd_group);
+               trunc(@wr_group);
+}
+${VIEW_USER:+
+               trunc(@rd_user);
+               trunc(@wr_user);
+}
+       }
+
+       /********************************************************************\
+        * END
+        ********************************************************************/
+EOF
+       pid=$!
+
+       #
+       # Identify child dtrace
+       #
+       if [ ! "$DEBUG" ]; then
+               info "Waiting for dtrace to initialize..."
+               while kill -0 $pid 2> /dev/null; do
+                       bpid=$( pgrep -P $pid dtrace ) && break
+                       sleep 1
+               done
+               if ! kill -0 $pid 2> /dev/null; then
+                       wait $pid > /dev/null 2>&1 # Collect exit status
+                       echo EXIT:$? # Send status to post-processor
+                       exit
+               fi
+       fi
+
+       #
+       # Wait on background (dtrace) child
+       #
+       status_collected=
+       while kill -0 $bpid > /dev/null 2>&1; do
+               wait > /dev/null 2>&1 # Collect exit status
+               [ "$status_collected" ] || status_collected=$?
+       done
+       echo EXIT:$status_collected # Send status to post-processor
+
+} | awk -v color=${COLOR:-0} \
+       -v cols=$cols \
+       -v cons=${CONS:-0} \
+       -v debug=${DEBUG:-0} \
+       -v debugger=${DEBUGGER:-0} \
+       -v group_map="$GROUP_MAP" \
+       -v interval=$INTERVAL_SECONDS \
+       -v ip_map="$IP_MAP" \
+       -v no_names=${NO_NAMES:-0} \
+       -v nsamples=${NSAMPLES:-0} \
+       -v output_json=${OUTPUT_JSON:-0} \
+       -v quiet=${QUIET:-0} \
+       -v passwd_map="$PASSWD_MAP" \
+       -v raw_view=${RAW_VIEW:-0} \
+       -v redact=${REDACT:-0} \
+       -v rows=$rows \
+       -v show_bytes=${SHOW_BYTES:-0} \
+       -v stderr=/dev/stderr \
+       -v tm=$( date +%s ) \
+       -v view="$VIEW" \
+       -v wide_view=${WIDE_VIEW:-0} \
+       '####################################### BEGIN
+
+       # ST: POST
+
+       BEGIN {
+               debug2("Terminal size (rows, cols) = (%d, %d)", rows, cols)
+
+               exit_status = 0 # SUCCESS
+               time_delta = 0 # Calculated
+               samples_left = nsamples
+
+               inv     = "\033[7m"
+               noinv   = "\033[27m"
+               bold    = "\033[1m"
+               nobold  = "\033[22m"
+               red     = "\033[31m"
+               green   = "\033[32m"
+               yellow  = "\033[33m"
+               magenta = "\033[35m"
+               cyan    = "\033[36m"
+               fgreset = "\033[39m"
+
+               # Obtain current process (awk) pid
+               (cmd = "echo $PPID") | getline apid
+               close(cmd)
+
+               # Obtain parent process (sh) pid
+               getline stat < (file = sprintf("/proc/%d/stat", apid))
+               close(file)
+               split(stat, st)
+               spid = st[4]
+
+               # Obtain parent process (sh) name
+               getline stat < (file = sprintf("/proc/%d/stat", spid))
+               close(file)
+               split(stat, st)
+               comm = st[2]
+               if (match(comm, /^\(.*\)$/))
+                       comm = substr(comm, 2, length(comm) - 2)
+
+               # Obtain child (sh) pid
+               (cmd = sprintf("pgrep -P %d %s", spid, comm)) | getline cpid
+               close(cmd)
+
+               if (!raw_view) {
+                       clear_data()
+                       resize()
+                       if (!no_names) load_files()
+               }
+
+               # Declare arrays
+               delete times
+
+               if (redact) {
+                       m = "^(USER|total|"
+                       if ((u = ENVIRON["USER"]) != "")
+                               m = m u "|"
+                       if ((s = ENVIRON["SUDO_USER"]) != "" && s != u)
+                               m = m s "|"
+                       cmd = "getent passwd 2> /dev/null"
+                       while (cmd | getline > 0) {
+                               if (split($0, f, /:/) < 3) continue
+                               if (f[3] > 1024 && f[3] !~ /^6553[456]$/)
+                                       continue
+                               m = m f[1] "|"
+                       }
+                       close(cmd)
+                       m = m "\\*)$"
+                       unredacted_users = m
+
+                       m = "^(GROUP|total|"
+                       cmd = "getent group 2> /dev/null"
+                       while (cmd | getline > 0) {
+                               if (split($0, f, /:/) < 3) continue
+                               if (f[3] > 1024 && f[3] !~ /^6553[456]$/)
+                                       continue
+                               m = m f[1] "|"
+                       }
+                       close(cmd)
+                       m = m "\\*)$"
+                       unredacted_groups = m
+
+                       unredacted_clients = "^(CLIENT|total|\\*)$"
+               }
+       }
+
+       ######################################## FUNCTIONS
+
+       function dtrace_init()
+       {
+               # Obtain handler (sh) pid
+               (cmd = sprintf("pgrep -P %d %s", cpid, comm)) | getline wpid
+
+               # Obtain dtrace pid
+               cmd = sprintf("pgrep -P %d dtrace", wpid)
+               cmd | getline bpid
+               close(cmd)
+       }
+
+       function debug1(fmt,a1) { if (debugger) printf fmt "\n", a1 }
+       function debug2(fmt,a1,a2) { if (debugger) printf fmt "\n", a1, a2 }
+       function debug3(fmt,a1,a2,a3) {
+               if (debugger) printf fmt "\n", a1, a2, a3
+       }
+       function buffer_add(text) { BUFFER = BUFFER text }
+
+       function print_buffer()
+       {
+               if (!cons && !output_json) buffer_add("\n")
+               printf "%s", BUFFER
+               fflush()
+       }
+
+       function info(str)
+       {
+               if (quiet) return
+               printf "%sINFO%s %s\n", color ? magenta : "",
+                       color ? fgreset : "", str > stderr
+               fflush(stderr)
+       }
+
+       function get_random(len,        c, n, r, rdata, rfile, rlen)
+       {
+               if (len < 1) return ""
+               rlen = 0
+               rdata = ""
+               rfile = "/dev/urandom"
+               while (length(rdata) < len && getline r < rfile > 0) {
+                       for (n = split(r, c, ""); n >= 1; n--) {
+                               if (c[n] !~ /[\x41-\x5a]/) continue
+                               rdata = rdata c[n]
+                               if (++rlen == len) break
+                       }
+               }
+               close(rfile)
+               return rdata
+       }
+
+       function resize(        dsz, vsz, vsz_fixed, bar_size_fixed,
+               bar_size_fixed_max, bar_size_fixed_min, bar_min1, bar_min2,
+               vsz_cols1, vsz_cols2, vsz_max, vsz_min, wv)
+       {
+               if (output_json) return
+
+               #
+               # Calculate columns and column widths
+               # ST: VIEWS
+               #
+               # NB: bar_size = size of bar column (if shown)
+               # NB: dsz = size of TOTAL, READ(OUT), WRITE(IN) data columns
+               # NB: vsz = size of VIEW column ("view size")
+               #
+               # If given -w (wide view) make bar_size fixed-width and
+               # vsz variable-width.
+               #
+               # Without -w, make vsz fixed-width and bar_size variable.
+               #
+
+               wv = wide_view
+
+               show_bar_column = 1
+               show_rw_columns = 1
+
+               vsz_min = length(view)
+               vsz_max = 15
+
+               dsz = show_bytes ? 10 : 12
+               bar_size_fixed_max = 21
+               bar_size_fixed_min = 11
+
+               #
+               # Calculate minimum terminal width required (bar_min1)
+               # to display small bar (bar_size_fixed_min) and also
+               # minimum terminal width required (bar_min2) to display
+               # larger bar (bar_size_fixed_max).
+               #
+               bar_min1 = 0
+               bar_min1 += vsz_min + 1 # VIEW + space
+               bar_min1 += dsz + 1 # TOTAL + space
+               vsz_cols2 = bar_min1
+               bar_min1 += dsz + 1 # WRITE(IN) + space
+               vsz_cols1 = bar_min2 = bar_min1
+               bar_min1 += bar_size_fixed_min + 1 # small bar + space
+               bar_min2 += bar_size_fixed_max + 1 # bigger bar + space
+               bar_min1 += dsz # READ(OUT)
+               bar_min2 += dsz # READ(OUT)
+               vsz_cols1 += dsz # READ(OUT)
+
+               #
+               # Calculate fixed bar width based on terminal width
+               # NB: Only used in wide-view (-w)
+               # NB: If terminal is too narrow, disable bar/columns
+               #
+               if (cols >= bar_min2) {
+                       bar_size_fixed = bar_size_fixed_max
+               } else if (cols >= bar_min1) {
+                       bar_size_fixed = bar_size_fixed_min
+               } else {
+                       show_bar_column = 0
+                       bar_size_fixed = 0
+               }
+
*** 950 LINES SKIPPED ***

Reply via email to