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