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
145  mainf "${@}"
146  rc="${?}"
147  exit "${rc}"
148
149
150