#!/bin/bash # Socktop systemtap script # Copyright (C) 2006 IBM Corp. # # This file is part of systemtap, and is free software. You can # redistribute it and/or modify it under the terms of the GNU General # Public License (GPL); either version 2, or (at your option) any # later version. ### ### socktop - Combination shell/systemtap script to track reads and writes ### on sockets by process. Can be filtered by process IDs and ### names, protocols, protocol families, users and socket type. ### ITER_COUNT=-1 # number of iterations (no iteration limit default) MOD_GEN=""; # Generate a instrumentation module option # Filter options F_PROTSTR=""; F_PROT=0 # Filter by protocol F_FAMSTR=""; F_FAM=0 # Filter by protocol family F_TYPESTR=""; F_TYPE=0 # Filter by socket type F_PIDSTR=""; F_PID=0 # Filter by process ID F_NAMESTR=""; F_NAME=0 # Filter by process name F_UIDSTR=""; F_UID=0 # Filter by user FILTER=0 # Any filters specified? # Print options P_INTERVAL=5 # default interval between output P_DEVICES=0 # default is don't display network device traffic P_NUMTOP=10 # default number of processes and network devices to print DELIM="," function usage { echo "USAGE: socktop [-d] [-i interval] [-N num] [-P protocol]... [-f family]..." echo " [-t stype]... [-n pname]... [-p pid]... [-u username]... [-h]" echo " -d # print network device traffic (default: off)" echo " -i interval # interval in seconds between printing (default: $P_INTERVAL)" echo " -N num # number of top processes and devices to print (default: $P_NUMTOP)" echo " -f family # this protocol family only (default: all)" echo " -P protocol # this protocol only (default: all)" echo " -t stype # this socket type only (default: all)" echo " -n pname # this process name only (default: all)" echo " -p pid # this process ID only (default: all)" echo " -u username # this user only (default: all)" echo " -c count # number of iteration" echo " -D # dry run (generate instrumentation but do not run)" echo " -h # print this help text" echo "" echo "Protocol Families:" echo " LOCAL, INET, INET6, IPX, NETLINK, X25, AX25, ATMPVC, APPLETALK, PACKET" echo "" echo "Protocols:" echo " TCP, UDP, SCTP, IP, FC, ... (see /etc/protocols for complete list)" echo "" echo "Socket Types:" echo " STREAM, DGRAM, RAW, RDM, SEQPACKET, DCCP, PACKET" } # Process options while getopts df:i:n:N:P:p:t:u:c:Dh option; do case $option in d) P_DEVICES=1 ;; i) P_INTERVAL=$OPTARG ;; N) P_NUMTOP=$OPTARG ;; f) let "F_FAM++" F_FAMSTR=$OPTARG$DELIM$F_FAMSTR ;; n) let "F_NAME++" F_NAMESTR=$OPTARG$DELIM$F_NAMESTR ;; p) let "F_PID++" F_PIDSTR=$OPTARG$DELIM$F_PIDSTR ;; P) let "F_PROT++" F_PROTSTR=$OPTARG$DELIM$F_PROTSTR ;; t) let "F_TYPE++" F_TYPESTR=$OPTARG$DELIM$F_TYPESTR ;; u) uid=`awk -F: '$1 == name {print $3}' name=$OPTARG /etc/passwd` if [[ $uid != "" ]]; then let "F_UID++" F_UIDSTR=$uid$DELIM$F_UIDSTR else echo "ERROR: Unknown user:" $OPTARG let "ERROR++" fi ;; c) ITER_COUNT=$OPTARG ;; D) MOD_GEN="-p4" ;; h|?|*) usage exit 1 ;; esac done if [[ $ERROR > 0 ]]; then exit 1 fi if [[ $F_FAM > 0 || $F_NAME > 0 || $F_PID > 0 || $F_PROT > 0 || $F_TYPE > 0 || $F_UID > 0 ]]; then FILTER=1 fi # # Pass a timezone adjustment value to the stap script # TZ=`date "+%z"` TZ_SIGN=`echo $TZ | cut -c1` TZ_HOURS=`echo $TZ | cut -c2-3` TZ_MINS=`echo $TZ | cut -c4-5` TZ_ADJUST=$TZ_SIGN$((10#$TZ_HOURS*60*60+10#$TZ_MINS*60)) # # Start the systemtap script # stap $MOD_GEN -w -e ' global iter global execname, user, if_tx, if_rx, if_dev global sk_tx, sk_rx, sk_pid global f_name_str, f_pid_str, f_prot_str, f_fam_str, f_type_str, f_uid_str global f_name, f_pid, f_prot, f_fam, f_type, f_uid probe never { #workaround to avoid outputing filtering strings log(f_name_str); log(f_pid_str); log(f_prot_str); log(f_fam_str); log(f_type_str); log(f_uid_str); #workaround to make sure that systemtap gets correct types for arrays if_rx["junk"] <<< 0 if_tx["junk"] <<< 0 if_dev["junk"] ++ delete if_rx delete if_tx delete if_dev } probe begin { # set number of iterations iter = '$ITER_COUNT' # If no filters specified, skip filter processing if ('$FILTER' == 0) next f_name_str = "'$F_NAMESTR'" f_pid_str = "'$F_PIDSTR'" f_prot_str = "'$F_PROTSTR'" f_fam_str = "'$F_FAMSTR'" f_type_str = "'$F_TYPESTR'" f_uid_str = "'$F_UIDSTR'" delim = "'$DELIM'" error = 0 # Protocols if ('$F_PROT') { prot = tokenize(f_prot_str, delim) while (prot != "") { p = sock_prot_str2num(prot) if (p < 0) { printf("ERROR: Unknown protocol: %s\n", prot) error++ } else f_prot[p] = 1 prot = tokenize("", delim) } } # Protocol families if ('$F_FAM') { fam = tokenize(f_fam_str, delim) while (fam != "") { f = sock_fam_str2num(fam) if (f < 0) { printf("ERROR: Unknown protocol family: %s\n", fam) error++ } else f_fam[f] = 1 fam = tokenize("", delim) } } # Process names if ('$F_NAME') { pname = tokenize(f_name_str, delim) while (pname != "") { f_name[pname] = 1 pname = tokenize("", delim) } } # Process IDs if ('$F_PID') { pid = tokenize(f_pid_str, delim) while (pid != "") { f_pid[strtol(pid, 10)] = 1 pid = tokenize("", delim) } } # Socket types if ('$F_TYPE') { stype = tokenize(f_type_str, delim) while (stype != "") { t = sock_type_str2num(stype) if (t < 0) { printf("ERROR: Unknown socket type: %s\n", stype) error++ } else f_type[t] = 1 stype = tokenize("", delim) } } # User IDs if ('$F_UID') { uid = tokenize(f_uid_str, delim) while (uid != "") { f_uid[strtol(uid, 10)] = 1 uid = tokenize("", delim) } } if (error) exit() } probe netdev.transmit { if ('$P_DEVICES') { if_tx[dev_name] <<< length if_dev[dev_name] ++ } } probe netdev.receive { if ('$P_DEVICES') { if_rx[dev_name] <<< length if_dev[dev_name] ++ } } probe socket.send { if (!success) next # Check filters if ('$FILTER') { if ('$F_PROT' && !(protocol in f_prot)) next if ('$F_FAM' && !(family in f_fam)) next if ('$F_PID' && !(pid() in f_pid)) next if ('$F_NAME' && !(execname() in f_name)) next if ('$F_UID' && !(uid() in f_uid)) next if ('$F_TYPE' && !(type in f_type)) next } execname[pid()] = execname() user[pid()] = uid() sk_tx[pid(), protocol, family] <<< size sk_pid[pid(), protocol, family] += size } probe socket.receive { if (!success) next # Check filters if ('$FILTER') { if ('$F_PROT' && !(protocol in f_prot)) next if ('$F_FAM' && !(family in f_fam)) next if ('$F_PID' && !(pid() in f_pid)) next if ('$F_NAME' && !(execname() in f_name)) next if ('$F_UID' && !(uid() in f_uid)) next if ('$F_TYPE' && !(type in f_type)) next } execname[pid()] = execname() user[pid()] = uid() sk_rx[pid(), protocol, family] <<< size sk_pid[pid(), protocol, family] += size } function print_activity() { # Print top processes max = '$P_NUMTOP' time = gettimeofday_s() + '$TZ_ADJUST' printf("======================= %s ========================\n", ctime(time)) printf("------------------------------- PROCESSES -------------------------------\n") printf("%-5s %-5s %7s %7s %7s %7s %-4s %-8s %-15s\n", "PID", "UID", "#SEND", "#RECV", "SEND_KB", "RECV_KB", "PROT", "FAMILY", "COMMAND") foreach ([pid, prot, fam] in sk_pid- limit max) { n_sk_tx = @count(sk_tx[pid, prot, fam]) n_sk_rx = @count(sk_rx[pid, prot, fam]) printf("%-5d %-5d %7d %7d %7d %7d %-4s %-8s %-15s\n", pid, user[pid], n_sk_tx, n_sk_rx, n_sk_tx ? @sum(sk_tx[pid, prot, fam])/1024 : 0, n_sk_rx ? @sum(sk_rx[pid, prot, fam])/1024 : 0, sock_prot_num2str(prot), sock_fam_num2str(fam), execname[pid]) } # Print top network devices if ('$P_DEVICES') { max = '$P_NUMTOP' printf("-------------------------------- DEVICES --------------------------------\n") printf("%-7s %13s %13s %15s %15s\n", "DEV", "#XMIT", "#RECV", "XMIT_KB", "RECV_KB") foreach ([dev] in if_dev- limit max) { n_if_tx = @count(if_tx[dev]) n_if_rx = @count(if_rx[dev]) printf("%-7s %13d %13d %15d %15d\n", dev, n_if_tx, n_if_rx, n_if_tx ? @sum(if_tx[dev])/1024 : 0, n_if_rx ? @sum(if_rx[dev])/1024 : 0) } } printf("=========================================================================\n\n") delete execname delete user delete sk_tx delete sk_rx delete sk_pid delete if_tx delete if_rx delete if_dev } probe timer.s('$P_INTERVAL') { print_activity(); --iter; if (iter == 0) exit(); } ' saved_status=$? # Cleanup test -n "$MOD_GEN" && rm -f ${MOD_NAME}.ko # Exit with the status of 'stap', not the cleanup commands. exit $saved_status