#!/bin/sh
# Copyright (c) 2015 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.
#
# This script contains a simple set of functions to analyze inotify
# file descriptor usage.
#
# script owner: sritchie

help() {
   echo "This utility inspects inotify file descriptor usage in various ways."
   echo "NOTE: run me as root, otherwise the current user limits inspection."
   echo ""
   echo "$0 count [timeout]"
   echo "   Print the total number of inotify fd's currently allocated. The optional"
   echo "   integer timeout will enable looping indefinitely while printing."
   echo "$0 limits"
   echo "   Print the maximum number of inotify watches and instances for each user."
   echo "   Also print the maximum number of queued events."
   echo "$0 ps"
   echo "   For each process using inotify, print the number of inotify fd's"
   echo "   owned by that process, the PID, and the /proc/PID/cmdline."
   echo "$0 tree <pid>"
   echo "   Given a parent pid, scan the entire tree of child processes and"
   echo "   print out each process usage count. Great for dumping the inotify"
   echo "   cost of an Abuild, for example."
   echo "$0 user"
   echo "   For each process using inotify, print out the number of fd's in use"
   echo "   by each user."
}

# print the number of inotify fd's currently being used by the system,
# optionally in a loop with a supplied timeout
count() {
   while [ true ]
   do
      echo -n "Number of Fds Used: "
      find /proc/*/fd/* -type l -lname 'anon_inode:inotify' 2>/dev/null | wc -l
      if [ -z $1 ]; then
         exit 0
      fi
      sleep $1
   done
   return 0
}

# prints all inotify limits on the system
limits() {
   echo -n "Max User Watches: "
   cat /proc/sys/fs/inotify/max_user_watches 
   echo -n "Max User Instances: "
   cat /proc/sys/fs/inotify/max_user_instances 
   echo -n "Max Queued Events: "
   cat /proc/sys/fs/inotify/max_queued_events
}

# print all the processes using inotify fd's and their usage count
ps() {
   STR=`find /proc/*/fd/* -type l -lname 'anon_inode:inotify' 2>/dev/null | cut -f 1-4 -d'/' | sort | uniq -c | sed 's/^ *//' | sort -nr`
 
   # Set the field separator to new line
   IFS=$'\n'
   
   echo -e "Fds Used by Each Process:\n"
   for item in $STR
   do
      if [[ $item =~ /proc/([0-9]*)/fd ]]; then
         CMD=`cat /proc/${BASH_REMATCH[1]}/cmdline`
         echo "$item  $CMD"
      fi
   done
   return 0
}

# starting from a given pid, print a sorted list of the children processes
# and their inotify count
tree() {
   if [ -z $1 ]; then
      echo "Please give me a parent pid"
      exit 1
   fi

   PARENT_PID=$1
   PIDS=`pstree -p $PARENT_PID -l | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/'`
   PROCS=''

   for pid in $PIDS
   do
      PROCS+='/proc/'$pid'/fd '
   done
   
   FDS=`find $PROCS -type l -lname 'anon_inode:inotify' 2>/dev/null | cut -f 1-4 -d'/' | sort | uniq -c | sed 's/^ *//' | sort -nr`
   
   # Set the field separator to new line
   IFS=$'\n'
   for item in $FDS
   do
      if [[ $item =~ /proc/([0-9]*)/fd ]]; then
         CMD=`cat /proc/${BASH_REMATCH[1]}/cmdline`
         echo "$item  $CMD"
      fi
   done

   return 0
}

# scan all available processes and print each user's inotify usage
user() {
   STR=`find /proc/*/fd/* -type l -lname 'anon_inode:inotify' 2>/dev/null`
   USERS=''

   for item in $STR
   do
      USERS+=`ls -l $item | cut -f 3 -d' '`$'\n'
   done
   
   echo -e "Fds Used by Each User:\n"
   echo "$USERS" | sed '$d' | sort | uniq -c | sed 's/^ *//' | sort -nr
}

case "$1" in
   count|tree)
      $1 $2
      ;;
   limits|ps|user|help)
      $1
      ;;
   *)
      echo "usage: $0 {count|limits|ps|tree|user|help}"
      exit 1
esac
