Advertisement
devinteske

nfsiosnoop

Jan 31st, 2020
1,321
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 13.17 KB | None | 0 0
  1. #!/bin/sh
  2. # vi: set ft=sh noet ts=8 sw=8 :: Vi/ViM
  3. ############################################################ IDENT(1)
  4. #
  5. # $Title: Script to snoop on NFS I/O under Linux $
  6. # $Copyright: 2020 Devin Teske. All rights reserved. $
  7. # $FrauBSD$
  8. #
  9. ############################################################ ENVIRONMENT
  10.  
  11. : "${USER:=$( id -nu )}" # Used to determine whether sudo(8) should be used
  12.  
  13. ############################################################ DEFAULTS
  14.  
  15. DEFAULT_TIME_FMT="%Y %b %e %T"
  16.  
  17. ############################################################ GLOBALS
  18.  
  19. VERSION='$Version: 3.0 $'
  20.  
  21. pgm="${0##*/}" # Program basename
  22.  
  23. #
  24. # Global exit status
  25. #
  26. SUCCESS=0
  27. FAILURE=1
  28.  
  29. #
  30. # Command-line options
  31. #
  32. DEBUG=              # -d
  33. QUIET=              # -q
  34. RAW_OUTPUT=         # -r
  35. TIME_FMT="$DEFAULT_TIME_FMT"    # -t fmt
  36. FILTER_GROUP=           # -g group
  37. FILTER_USER=            # -u user
  38. VERBOSE=            # -v
  39.  
  40. #
  41. # Miscellaneous
  42. #
  43. COMM=
  44. CONS=1
  45. [ -t 1 ] || CONS= # stdout is not a tty
  46. FILTER_GID=
  47. FILTER_UID=
  48. RAW_TIME_FMT='"%s"'
  49.  
  50. ############################################################ FUNCTIONS
  51.  
  52. if [ "$CONS" ]; then
  53. info(){ printf "\033[35mINFO\033[39m %s\n" "$*" >&2; }
  54. else
  55. info(){ printf "INFO %s\n" "$*" >&2; }
  56. fi
  57.  
  58. usage()
  59. {
  60.     local optfmt="\t%-11s %s\n"
  61.     exec >&2
  62.     printf "Usage: %s [OPTIONS] [comm]\n" "$pgm"
  63.     printf "Options:\n"
  64.     printf "$optfmt" "-d" "Debug. Print script and exit."
  65.     printf "$optfmt" "-h" "Print usage statement and exit."
  66.     printf "$optfmt" "-q" "Quiet. Hide dup*, open/close, and null r/w."
  67.     printf "$optfmt" "-r" "Show unformatted raw output."
  68.     printf "$optfmt" "-t fmt" "Time format. Default \`$DEFAULT_TIME_FMT'."
  69.     printf "$optfmt" "-u user" "Filter on user. Can be name or UID."
  70.     printf "$optfmt" "-g group" "Filter on group. Can be name or GID."
  71.     printf "$optfmt" "-V" "Print version and exit."
  72.     printf "$optfmt" "-v" "Verbose. Show filenames and hidden tracepoints."
  73.     exit $FAILURE
  74. }
  75.  
  76. lsofN()
  77. {
  78.     [ "$DEBUG" ] && return
  79.  
  80.     case "$USER" in
  81.     root) lsof -N +c 0 ;;
  82.     *) sudo lsof -N +c 0
  83.     esac
  84. }
  85.  
  86. run()
  87. {
  88.     if [ "$DEBUG" ]; then
  89.         cat
  90.         return
  91.     fi
  92.  
  93.     case "$USER" in
  94.     root) bpftrace -B none /dev/stdin "$@" ;;
  95.     *) sudo bpftrace -B none /dev/stdin "$@"
  96.     esac
  97. }
  98.  
  99. ############################################################ MAIN
  100.  
  101. #
  102. # Process command-line options
  103. #
  104. while getopts dg:hqrt:u:Vv flag; do
  105.     case "$flag" in
  106.     d) DEBUG=1 ;;
  107.     g) FILTER_GROUP="$OPTARG" ;;
  108.     q) QUIET=1 ;;
  109.     r) RAW_OUTPUT=1 ;;
  110.     t) TIME_FMT="$OPTARG" ;;
  111.     u) FILTER_USER="$OPTARG" ;;
  112.     V) VERSION="${VERSION#*: }"
  113.         echo "${VERSION% $}"
  114.         exit $SUCCESS ;;
  115.     v) VERBOSE=1 ;;
  116.     *) usage # NOTREACHED
  117.     esac
  118. done
  119. shift $(( $OPTIND - 1 ))
  120.  
  121. #
  122. # Process command-line arguments
  123. #
  124. COMM="$1"
  125.  
  126. #
  127. # Process user/group options
  128. #
  129. case "$FILTER_USER" in
  130. "") : leave-empty ;;
  131. *[!0-9]*) # Translate from name to UID
  132.     FILTER_UID=$(
  133.         getent passwd "$FILTER_USER" | awk -F: '{print $3}'
  134.     ) || exit
  135.     ;;
  136. *) # Translate from UID to name
  137.     FILTER_UID=$FILTER_USER
  138.     FILTER_USER=$(
  139.         getent passwd $FILTER_UID | awk -F: '{print $1}'
  140.     ) || exit
  141. esac
  142. case "$FILTER_GROUP" in
  143. "") : leave-empty ;;
  144. *[!0-9]*) # Translate from name to GID
  145.     FILTER_GID=$(
  146.         getent group "$FILTER_GROUP" | awk -F: '{print $3}'
  147.     ) || exit
  148.     ;;
  149. *) # Translate from GID to name
  150.     FILTER_GID=$FILTER_GROUP
  151.     FILTER_GROUP=$(
  152.         getent group $FILTER_GID | awk -F '{print $1}'
  153.     ) || exit
  154. esac
  155.  
  156. #
  157. # Inform the user what we are doing
  158. #
  159. [ "$DEBUG" ] || info "Tracing NFS... Hit Ctrl-C to end."
  160.  
  161. #
  162. # Run script
  163. # NB: M-x package-install [RET] dtrace-script-mode [RET]
  164. #
  165. {
  166.     # List current open connections
  167.     lsofN | awk \
  168.         -v filter_user="$FILTER_USER" \
  169.         -v timefmt="$TIME_FMT" \
  170.         -v verbose=${VERBOSE:-0} \
  171.     '################################################# BEGIN
  172.  
  173.     BEGIN {
  174.         if (timefmt != "") {
  175.             srand()
  176.             time = srand()
  177.         }
  178.     }
  179.  
  180.     ################################################## MAIN
  181.  
  182.     (type = $5) != "DIR" && (fd = $4) ~ /^[0-9]+[rwu-]$/ {
  183.         cmd = $1
  184.         pid = $2
  185.         user = $3
  186.         if (filter_user != "" && user != filter_user) next
  187.         filename = $0
  188.         sub("^[^/]*/", "/", filename)
  189.         sub(/ \([^)]+\)$/, "", filename)
  190.         if (verbose) {
  191.             printf "%s|enter_open|%s|%s|%u|%s\n",
  192.                 timefmt == "" ? "" : time,
  193.                 user, cmd, pid, filename
  194.         }
  195.         printf "%s|exit_open|%s|%s|%u|%d|1\n",
  196.             timefmt == "" ? "" : time, user, cmd, pid, fd
  197.     }
  198.     ' # END-QUOTE
  199.  
  200.     run "$@" <<EOF
  201.     /* -*- mode: dtrace-script; tab-width: 4 -*- ;; Emacs
  202.      * vi: set ft=dtrace noet ts=4 sw=4 :: Vi/ViM
  203.      */
  204.  
  205.     #include <linux/fs.h>
  206.     #include <linux/mount.h>
  207.  
  208.     /*
  209.      * open(2)/openat(2) [enter] probes
  210.      */
  211.  
  212. $( for func in sys_enter_open sys_enter_openat; do
  213.     name=${func#*enter_}
  214.     printf "\ttracepoint:syscalls:%s\n" $func
  215.     condition=
  216.     [ ! "$COMM" ] || condition="$condition && comm == str(\$1)"
  217.     [ ! "$FILTER_GID" ] || condition="$condition && gid == $FILTER_GID"
  218.     [ ! "$FILTER_UID" ] || condition="$condition && uid == $FILTER_UID"
  219.     [ ! "$condition" ] || printf "\t/%s/\n" "${condition# && }"
  220. cat <<PROBE
  221.     {
  222.         delete(@vfs_${name}_nfs[tid]); // Flag from vfs_open
  223.         @vfs_$name[tid] = 1; // Flag for vfs_open${VERBOSE:+
  224. `cat <<VERBOSE
  225. ${TIME_FMT:+
  226.         time($RAW_TIME_FMT);}
  227.         printf("|${func#sys_}|%d.%d|%s|%d|%s\n",
  228.             uid, gid, comm, pid, str(args->filename));
  229. VERBOSE`}
  230.     }
  231.  
  232. PROBE
  233. done )
  234.  
  235.     /*
  236.      * Kernel probes
  237.      * NB: vfs_open() always called after open(2)/openat(2)
  238.      */
  239.  
  240. $( for func in sys_enter_open sys_enter_openat; do
  241.     name=${func#*enter_}
  242.     exit_func=${func%%enter_*}exit_$name
  243. cat <<PROBE
  244.     kprobe:vfs_open
  245.     /@vfs_$name[tid]/
  246.     {
  247.         delete(@vfs_$name[tid]); // Flag from $func
  248.  
  249.         //
  250.         // Extract the filesystem type from mount superblock
  251.         //
  252.         \$path = (struct path *)arg0;
  253.         \$mnt = (struct vfsmount *)\$path->mnt;
  254.         \$mnt_sb = (struct super_block *)\$mnt->mnt_sb;
  255.         \$type = (struct file_system_type *)\$mnt_sb->s_type;
  256.         \$fstype = \$type->name;
  257.  
  258.         //
  259.         // Test for NFS
  260.         //
  261.         if (str(\$fstype) == "nfs") {
  262.             @vfs_${name}_nfs[tid] = 1; // Flag for $exit_func
  263.         }
  264.     }
  265.  
  266. PROBE
  267. done )
  268.  
  269.     /*
  270.      * open(2)/openat(2) [exit] probes
  271.      */
  272.  
  273. $( for func in sys_exit_open sys_exit_openat; do
  274.     name=${func#*exit_}
  275. cat <<PROBE
  276.     tracepoint:syscalls:$func
  277.     /@vfs_${name}_nfs[tid]/
  278.     {
  279.         delete(@vfs_${name}_nfs[tid]); // Flag from vfs_open
  280.  
  281.         \$ret = args->ret;
  282.         if (\$ret >= 0)
  283.         {
  284.             \$fd = (uint64)\$ret;
  285.  
  286.             // Set flag to trace read/write on fd
  287.             @trace[pid, \$fd] = 1;${VERBOSE:+
  288. `cat <<VERBOSE
  289. ${TIME_FMT:+
  290.             time($RAW_TIME_FMT);}
  291.             printf("|${func#sys_}|%d.%d|%s|%d|%d|1\n",
  292.                 uid, gid, comm, pid, \\\$fd);
  293.         }
  294.         else
  295.         {${TIME_FMT:+
  296.             time($RAW_TIME_FMT);}
  297.             printf("|${func#sys_}|%d.%d|%s|%d|%d|0\n",
  298.                 uid, gid, comm, pid, \\\$fd);
  299. VERBOSE`}
  300.         }
  301.     }
  302.  
  303. PROBE
  304. done )
  305.  
  306.     /*
  307.      * dup*(2) probes
  308.      */
  309.  
  310.     tracepoint:syscalls:sys_enter_dup
  311.     /@trace[pid, args->fildes]/
  312.     {
  313.         \$fd = (int64)args->fildes;
  314.  
  315.         @dup_fd[tid] = \$fd; // Pass to sys_exit_dup
  316.     }
  317.  
  318.     tracepoint:syscalls:sys_exit_dup
  319.     /@dup_fd[tid]/
  320.     {
  321.         \$oldfd = (int64)@dup_fd[tid];
  322.         \$newfd = (int64)args->ret;
  323.  
  324.         delete(@dup_fd[tid]); // Arg from sys_enter_dup
  325.  
  326.         if (\$oldfd != \$newfd)
  327.         {
  328.             // Set flag to trace read/write on fd
  329.             @trace[pid, \$newfd] = 1;
  330. ${TIME_FMT:+
  331.             time($RAW_TIME_FMT);}
  332.             printf("|dup|%d.%d|%s|%d|%d|",
  333.                 uid, gid, comm, pid, \$oldfd);
  334.             printf("%d\n", \$newfd);
  335.         }
  336.  
  337.     }
  338.  
  339.     tracepoint:syscalls:sys_enter_dup2
  340.     /@trace[pid, args->oldfd]/
  341.     {
  342.         \$oldfd = (int64)args->oldfd;
  343.         \$newfd = (int64)args->newfd;
  344.  
  345.         if (\$oldfd != \$newfd)
  346.         {
  347.             // Set flag to trace read/write on fd
  348.             @trace[pid, \$newfd] = 1;
  349. ${TIME_FMT:+
  350.             time($RAW_TIME_FMT);}
  351.             printf("|dup2|%d.%d|%s|%d|%d|",
  352.                 uid, gid, comm, pid, \$oldfd);
  353.             printf("%d\n", \$newfd);
  354.         }
  355.     }
  356.  
  357.     tracepoint:syscalls:sys_enter_dup3
  358.     /@trace[pid, args->oldfd]/
  359.     {
  360.         \$oldfd = (int64)args->oldfd;
  361.         \$newfd = (int64)args->newfd;
  362.  
  363.         if (\$oldfd != \$newfd)
  364.         {
  365.             // Set flag to trace read/write on fd
  366.             @trace[pid, \$newfd] = 1;
  367. ${TIME_FMT:+
  368.             time($RAW_TIME_FMT);}
  369.             printf("|dup3|%d.%d|%s|%d|%d|",
  370.                 uid, gid, comm, pid, \$oldfd);
  371.             printf("%d\n", \$newfd);
  372.         }
  373.     }
  374.  
  375.     /*
  376.      * close(2) probe
  377.      */
  378.  
  379.     tracepoint:syscalls:sys_enter_close
  380.     /@trace[pid, args->fd]/
  381.     {
  382.         \$fd = args->fd;
  383.  
  384.         delete(@trace[pid, \$fd]); // Stop tracing read/write on fd
  385. ${VERBOSE:+
  386. `cat <<VERBOSE
  387. ${TIME_FMT:+
  388.         time($RAW_TIME_FMT);}
  389.         printf("|close|%d.%d|%s|%d|%d\n", uid, gid, comm, pid, \\\$fd);
  390. VERBOSE`}
  391.     }
  392.  
  393.     /*
  394.      * Read/Write probes
  395.      */
  396.  
  397.     tracepoint:syscalls:sys_enter_read
  398.     /@trace[pid, args->fd]/
  399.     {
  400.         \$fd = args->fd;
  401.         \$count = args->count;
  402.  
  403.         // Pass args to sys_exit_read
  404.         @read_fd[tid] = \$fd;
  405.         @read_count[pid, \$fd] = \$count;
  406.     }
  407.  
  408.     tracepoint:syscalls:sys_exit_read
  409.     /@read_fd[tid]/
  410.     {
  411.         \$fd = @read_fd[tid];
  412.         \$count = @read_count[pid, \$fd];
  413.         \$ret = args->ret;
  414.  
  415.         // Args from sys_enter_read
  416.         delete(@read_fd[tid]);
  417.         delete(@read_count[pid, \$fd]);
  418. ${TIME_FMT:+
  419.         time($RAW_TIME_FMT);}
  420.         printf("|read|%d.%d|%s|%d|%d|", uid, gid, comm, pid, \$fd);
  421.         printf("%ld|%ld\n", \$count, \$ret);
  422.     }
  423.  
  424.     tracepoint:syscalls:sys_enter_write
  425.     /@trace[pid, args->fd]/
  426.     {
  427.         \$fd = args->fd;
  428.         \$count = args->count;
  429.  
  430.         // Pass args to sys_exit_write
  431.         @write_fd[tid] = \$fd;
  432.         @write_count[pid, \$fd] = \$count;
  433.     }
  434.  
  435.     tracepoint:syscalls:sys_exit_write
  436.     /@write_fd[tid]/
  437.     {
  438.         \$fd = @write_fd[tid];
  439.         \$count = @write_count[pid, \$fd];
  440.         \$ret = args->ret;
  441.  
  442.         // Args from sys_enter_write
  443.         delete(@write_fd[tid]);
  444.         delete(@write_count[pid, \$fd]);
  445. ${TIME_FMT:+
  446.         time($RAW_TIME_FMT);}
  447.         printf("|write|%d.%d|%s|%d|%d|", uid, gid, comm, pid, \$fd);
  448.         printf("%ld|%ld\n", \$count, \$ret);
  449.     }
  450.  
  451.     END {
  452.         clear(@dup_fd);
  453.         clear(@read_count);
  454.         clear(@read_fd);
  455.         clear(@trace);
  456.         clear(@vfs_open);
  457.         clear(@vfs_open_nfs);
  458.         clear(@vfs_openat);
  459.         clear(@vfs_openat_nfs);
  460.         clear(@write_count);
  461.         clear(@write_fd);
  462.     }
  463. EOF
  464. } | awk -F'|' \
  465.     -v cons=${CONS:-0} \
  466.     -v debug=${DEBUG:-0} \
  467.     -v quiet=${QUIET:-0} \
  468.     -v raw=${RAW_OUTPUT:-0} \
  469.     -v verbose=${VERBOSE:-0} \
  470.     -v stderr=/dev/stderr \
  471.     -v timefmt="$TIME_FMT" \
  472.     '################################################# BEGIN
  473.  
  474.     BEGIN {
  475.         red   = "\033[31m"
  476.         green = "\033[32m"
  477.         cyan  = "\033[36m"
  478.         frset = "\033[39m"
  479.  
  480.         srand() # Seed clock
  481.         tlim = srand() * 2 # limit time outliers
  482.     }
  483.  
  484.     ################################################## FUNCTIONS
  485.  
  486.     function key(a, b) { return a "," b }
  487.     function fprint() { print; fflush() }
  488.     function eprint() { print > stderr; fflush(stderr) }
  489.  
  490.     function emit(color, str)
  491.     {
  492.         if (timefmt != "") {
  493.             if (time !~ /^[0-9]+$/ || time > tlim || time < 0)
  494.                 return
  495.             printf "%s%s%s %s %s[%u]: %s\n",
  496.                 cons ? color : "", strftime(timefmt, time),
  497.                 cons ? frset : "", user, cmd, pid, str;
  498.         } else {
  499.             printf "%s %s[%u]: %s\n", user, cmd, pid, str;
  500.         }
  501.         fflush()
  502.     }
  503.  
  504.     ################################################## MAIN
  505.  
  506.     debug { sub(/^\t/, ""); fprint(); next }
  507.     raw { fprint(); next }
  508.     /^Attaching [0-9]+ probes/ { eprint(); next }
  509.  
  510.     # Fields
  511.     { time = $1 }
  512.     timefmt != "" {
  513.         if (time == "" || time < 0) next
  514.     }
  515.     NF < 6 { next }
  516.     {
  517.         call = $2
  518.         user = $3
  519.         cmd = $4
  520.         pid = $5
  521.         if (call == "enter_open" || call == "enter_openat") {
  522.             filename = $6
  523.         } else {
  524.             fd = $6
  525.         }
  526.     }
  527.  
  528.     call == "enter_open" || call == "enter_openat" {
  529.         filename_cache[pid] = filename
  530.         next
  531.     }
  532.  
  533.     call == "exit_open" || call == "exit_openat" {
  534.         callname = substr(call, 6)
  535.         if ($7 == 1) { # NFS
  536.             filename = filename_cache[pid]
  537.             delete filename_cache[pid]
  538.             filename_cache[key(pid, fd)] = filename
  539.             if (verbose && !quiet) {
  540.                 emit(green, sprintf("%s(filename=%s) = %d",
  541.                     callname, filename, fd))
  542.             }
  543.         } else {
  544.             delete filename_cache[pid]
  545.         }
  546.         next
  547.     }
  548.  
  549.     call == "dup" {
  550.         if (!verbose) next
  551.         filename = filename_cache[key(pid, fd)]
  552.         newfd = $7
  553.         filename_cache[key(pid, newfd)] = filename
  554.         if (!quiet) {
  555.             emit(green, sprintf("dup(fd=%d <%s>) = %d",
  556.                 fd, filename, newfd))
  557.         }
  558.         next
  559.     }
  560.  
  561.     call == "dup2" || call == "dup3" {
  562.         if (!verbose) next
  563.         filename = filename_cache[key(pid, fd)]
  564.         newfd = $7
  565.         filename_cache[key(pid, newfd)] = filename
  566.         if (!quiet) {
  567.             emit(green, sprintf("%s(oldfd=%d <%s>, newfd=%d)",
  568.                 call, fd, filename, newfd))
  569.         }
  570.         next
  571.     }
  572.  
  573.     call == "close" {
  574.         if (!verbose) next
  575.         k = key(pid, fd)
  576.         filename = filename_cache[k]
  577.         delete filename_cache[k]
  578.         if (!quiet) {
  579.             emit(green, sprintf("close(fd=%d <%s>)", fd, filename))
  580.         }
  581.         next
  582.     }
  583.  
  584.     call == "read" || call == "write" {
  585.         ret = $8
  586.         if (quiet && ret == 0) next
  587.         count = $7
  588.         filename = filename_cache[key(pid, fd)]
  589.         if (verbose) {
  590.             emit(call == "read" ? cyan : red,
  591.                 sprintf("%s(fd=%d <%s>, count=%ld) = %ld",
  592.                 call, fd, filename, count, ret))
  593.         } else if (ret > 0) {
  594.             emit(call == "read" ? cyan : red,
  595.                 sprintf("%s(fd=%d, count=%ld) = %ld",
  596.                 call, fd, count, ret))
  597.         }
  598.         next
  599.     }
  600. ' # END-QUOTE
  601.  
  602. ################################################################################
  603. # END
  604. ################################################################################
  605. # Local Variables:
  606. # mode: sh
  607. # tab-width: 8
  608. # sh-basic-offset: 8
  609. # indent-tabs-mode: t
  610. # backward-delete-char-untabify-method: nil
  611. # End:
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement