From nobody Sun Sep 06 22:36:51 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hdQ6q30Fqz6qr43 for ; Sun, 06 Sep 2026 22:36:51 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hdQ6q2lnxz3cmN for ; Sun, 06 Sep 2026 22:36:51 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1788734211; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=FitaS85t4p4MFNJMbIGGRA/flqa+GZa4nvxW2RVGFc8=; b=uJjORxWO1RoiUuFLfDjX2VzAB+99fPJZMKAT68pTJBveGVyeewQqAjSlL1iO6m7eHpIgQO U5zc7e2OYkgaaC6ljHZNUAu+vD4aYQvDGZqMiy88/lyxPkvnL+QZEQVT2uVS4uT2Sd4TTu 4fE7L12hGX/q/NSg0/P71tmZl776IekjZE4B8C4ttTQp7az13hPWo6aafCFkAshMEaqREh zZ7Z2dskwE0qR0mq2w1l2QEIrT17pLMJnKBEJPY7OY+vdosBAtKS49P+SkSduwLvex+pU1 L8p13SX7bMchx1q0hQ5LfhxFMdAhaSIo56L1U96w65yWpXpkqSt5Um99EeJUiA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1788734211; a=rsa-sha256; cv=none; b=qIFg1lX/1WVS9HryL9V0k4+7/zt9saqzXxe52nJvErHEp/ezoXtOlIN3r1tF6dT3CHlHS1 WTyyII5zmb5zr4PYuxNWUZZhAaid6g7MdZpYSOZzGDUXkEr0vwmQ4oi2gAVB4dSdEo5VD0 76V+WCTB9dFKXH38fsVi+yKcZqiEpg+B3Xx9YEfKumxFowfdVoL2Koanl3cS+QNmFLT42S LNI+qoO63bFZQn4CQ1xRVnD/U3mJtJ37KWBLxdXfFiY+sDDLGXPEkYih/SBgTEV7CV098I yY3y6Ni548cn65fMN7blvLfiWw+wrDbrrknVM6GHeHNOapgh6uv3m/fWXzBnfA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1788734211; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=FitaS85t4p4MFNJMbIGGRA/flqa+GZa4nvxW2RVGFc8=; b=fH39cdmXpjYgVN7hSiwkk3U8bQQmh/H1+LQfQF9aTDC1fV5HytsCmdSDju9nJRxPRy/vrJ bzBCb+aktoN6A9YuOEC+TrDMnwdnDQfJ7geSfju9WxpZ8kcjYCs2se1rWlELwa2ypuyrOl aGeSi4+RIUorOCu3hny6zLKL0wh8wZ1B+WbdQaPfYQfHjtMbB2XNFXffETKi7TgXa7r1yR OHAL4J4qEaoQiNH5JCwfq3SAUrSWKPaK0hYrE4opYOTYnkHeLz4Rt8hQIf4AUPriu0iwM9 UdZAyM6SAKXQdFN8vMH6TJ0U1kHNRYb3PiXgeWbVk6B77mLoA5Q2CP6s2w0/0w== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hdQ6q1T1bzrss for ; Sun, 06 Sep 2026 22:36:51 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 1ee7b by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 06 Sep 2026 22:36:51 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Devin Teske Subject: git: 60c7313074b9 - main - Introduce nfsdtop(8) List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: dteske X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 60c7313074b93adf4ea70ff44b78b3b4fc15c29f Auto-Submitted: auto-generated Date: Sun, 06 Sep 2026 22:36:51 +0000 Message-Id: <6a9deb03.1ee7b.5e2f320f@gitrepo.freebsd.org> The branch main has been updated by dteske: URL: https://cgit.FreeBSD.org/src/commit/?id=60c7313074b93adf4ea70ff44b78b3b4fc15c29f commit 60c7313074b93adf4ea70ff44b78b3b4fc15c29f Author: Devin Teske AuthorDate: 2026-09-06 22:34:34 +0000 Commit: Devin Teske 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 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 < { + 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 + ((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 + ((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 ***