sangeri-usĀ | b3c1375 | 2017-11-08 07:26:16 -0600 | [diff] [blame] | 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 | |
| 13 | program_name=${0##*/} |
| 14 | temp_file_path_1=/tmp/${program_name}_results_1 |
| 15 | temp_file_path_2=/tmp/${program_name}_results_2 |
| 16 | temp_file_path_3=/tmp/${program_name}_results_3 |
| 17 | |
| 18 | temp_file_list="${temp_file_path_1} ${temp_file_path_2} ${temp_file_path_3}" |
| 19 | csv_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 | |
| 25 | function 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 | |
| 36 | function exit_function { |
| 37 | |
| 38 | # Used to clean up tmp files. |
| 39 | |
| 40 | rm -f ${temp_file_list} |
| 41 | return |
| 42 | |
| 43 | } |
| 44 | |
| 45 | |
| 46 | function 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 | |
| 62 | function 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 | |
| 89 | function 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 | |
| 145 | mainf "${@}" |
| 146 | rc="${?}" |
| 147 | exit "${rc}" |
| 148 | |
| 149 | |