1#!/bin/bash 2 3# This program will calculate memory usage for each process and generate a 4# comma-separated value (CSV) output file named output.csv in the current 5# directory. The output will consist of 2 lines. The first is a comma- 6# separated list of process names. The second is a list of comma-separated 7# memory usage values (expressed in bytes). Here is an abbrieviated example of 8# the output: 9# python(9),/lib/systemd/systemd-journald,/usr/bin/python,/sbin/init, 10# phosphor-hwmon-readd(4),ipmid,phosphor-ledcontroller(4) 11# 57896960,11789312,4434944,2893824,1900544,1764352 12 13program_name=${0##*/} 14temp_file_path_1=/tmp/${program_name}_results_1 15temp_file_path_2=/tmp/${program_name}_results_2 16temp_file_path_3=/tmp/${program_name}_results_3 17 18temp_file_list="${temp_file_path_1} ${temp_file_path_2} ${temp_file_path_3}" 19csv_file_path="output.csv" 20 21# Description of argument(s): 22# pid The pid for which you desire statistics. If this is not specified, 23# statistics will be gathered for all active pids. 24 25function get_parms() { 26 27 # Get program parms. 28 29 pids="${1:-$(ls /proc | grep -v [A-Za-z])}" ; shift 30 31 return 0 32 33} 34 35 36function exit_function() { 37 38 # Used to clean up tmp files. 39 40 rm -f ${temp_file_list} 41 return 42 43} 44 45 46function validate_parms() { 47 48 # Validate program parameters. 49 50 # Making sure only root can run our script. 51 if [ "${USER}" != "root" ] ; then 52 echo "This script must be run as root" 1>&2 53 return 1 54 fi 55 56 trap "exit_function $signal \$?" EXIT 57 return 0 58 59} 60 61 62function get_process_mem() { 63 64 local pid="${1}" ; shift 65 # Count memory statistic for passed pid. 66 67 # Description of argument(s): 68 # pid The process ID for which you desire statistics. 69 [ ! -f /proc/${pid}/status -o ! -f /proc/${pid}/smaps ] && return 0 70 71 # pss_total Total proportional set size of a process. 72 # private_total Total number of clean and dirty private pages in the 73 # mapping. 74 # shared_total The difference between pss_total and private_total. 75 76 local pss_total private_total shared_total sum name 77 pss_total=$(grep -e "^Pss:" /proc/${pid}/smaps | awk '{print $2}' | awk '{sum += $1} END {print sum}') 78 private_total=$(grep -e "^Private" /proc/${pid}/smaps | awk '{print $2}' | awk '{sum += $1} END {print sum}') 79 80 [ -z "${pss_total}" -o -z "${private_total}" ] && return 0 81 (( shared_total=pss_total-private_total )) 82 name=$(cut -d "" -f 1 /proc/${pid}/cmdline) 83 (( sum=shared_total+private_total )) 84 echo -e "${private_total} + ${shared_total} = ${sum} ${name}" 85 86} 87 88 89function mainf() { 90 91 get_parms "$@" || return 1 92 93 validate_parms || return 1 94 95 # To ensure temp files not exist. 96 rm -f ${temp_file_list} 97 98 for pid in ${pids} ; do 99 get_process_mem ${pid} >> ${temp_file_path_1} 100 done 101 102 # This is used to sort results by memory usage. 103 sort -gr -k 5 ${temp_file_path_1} > ${temp_file_path_2} 104 105 # Find duplicates in the process list output and combine them. In such 106 # cases, adjust the process name by including a (<count>) suffix. In the 107 # following example of output, 4 instances of "phosphor-hwmon-readd" have 108 # been combined. 109 # 974848 + 925696 = 1900544 phosphor-hwmon-readd(4) 110 for proc_name in $(awk '{print $6}' ${temp_file_path_2} | sort -u) ; do 111 count=$(awk -v src=${proc_name} '{if ($6==src) {print $6}}' ${temp_file_path_2} | wc -l) 112 [ "${count}" = "1" ] && count_string="" || count_string="(${count})" 113 vmsize_in_kb=$(awk -v src=${proc_name} '{if ($6==src) {print $1}}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}') 114 vmrss_in_kb=$(awk -v src=${proc_name} '{if ($6==src) {print $3}}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}') 115 total=$(awk '{print $5}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}') 116 (( sum=vmrss_in_kb+vmsize_in_kb )) 117 echo -e "${vmsize_in_kb} + ${vmrss_in_kb} = ${sum} \t ${proc_name}${count_string}" >> ${temp_file_path_3} 118 done 119 120 # Sort once more. 121 sort -gr -k 5 ${temp_file_path_3} > ${temp_file_path_1} 122 123 # Read results from temp file and convert it to csv. 124 csv_line1="" 125 csv_line2="" 126 while read line ; do 127 while read private plus_operator shared equal_sign sum name ; do 128 (( sum == 0 )) && continue 129 csv_line1+=",${name}" 130 csv_line2+=",${sum}" 131 done <<<${line} 132 done < ${temp_file_path_1} 133 134 # Strip leading commas. 135 csv_line1="${csv_line1#,}" 136 csv_line2="${csv_line2#,}" 137 { echo "${csv_line1}" ; echo "${csv_line2}" ; } >> ${csv_file_path} 138 139 return 0 140 141} 142 143# Main 144 145mainf "${@}" 146rc="${?}" 147exit "${rc}" 148 149