blob: 4ee99778800bcdc913cc27406d2d1bc70a7ae26c [file] [log] [blame]
#!/bin/bash
# This program will calculate memory usage for each process and generate a
# comma-separated value (CSV) output file named output.csv in the current
# directory. The output will consist of 2 lines. The first is a comma-
# separated list of process names. The second is a list of comma-separated
# memory usage values (expressed in bytes). Here is an abbrieviated example of
# the output:
# python(9),/lib/systemd/systemd-journald,/usr/bin/python,/sbin/init,
# phosphor-hwmon-readd(4),ipmid,phosphor-ledcontroller(4)
# 57896960,11789312,4434944,2893824,1900544,1764352
program_name=${0##*/}
temp_file_path_1=/tmp/${program_name}_results_1
temp_file_path_2=/tmp/${program_name}_results_2
temp_file_path_3=/tmp/${program_name}_results_3
temp_file_list="${temp_file_path_1} ${temp_file_path_2} ${temp_file_path_3}"
csv_file_path="output.csv"
# Description of argument(s):
# pid The pid for which you desire statistics. If this is not specified,
# statistics will be gathered for all active pids.
function get_parms() {
# Get program parms.
pids="${1:-$(ls /proc | grep -v [A-Za-z])}" ; shift
return 0
}
function exit_function() {
# Used to clean up tmp files.
rm -f ${temp_file_list}
return
}
function validate_parms() {
# Validate program parameters.
# Making sure only root can run our script.
if [ "${USER}" != "root" ] ; then
echo "This script must be run as root" 1>&2
return 1
fi
trap "exit_function $signal \$?" EXIT
return 0
}
function get_process_mem() {
local pid="${1}" ; shift
# Count memory statistic for passed pid.
# Description of argument(s):
# pid The process ID for which you desire statistics.
[ ! -f /proc/${pid}/status -o ! -f /proc/${pid}/smaps ] && return 0
# pss_total Total proportional set size of a process.
# private_total Total number of clean and dirty private pages in the
# mapping.
# shared_total The difference between pss_total and private_total.
local pss_total private_total shared_total sum name
pss_total=$(grep -e "^Pss:" /proc/${pid}/smaps | awk '{print $2}' | awk '{sum += $1} END {print sum}')
private_total=$(grep -e "^Private" /proc/${pid}/smaps | awk '{print $2}' | awk '{sum += $1} END {print sum}')
[ -z "${pss_total}" -o -z "${private_total}" ] && return 0
(( shared_total=pss_total-private_total ))
name=$(cut -d "" -f 1 /proc/${pid}/cmdline)
(( sum=shared_total+private_total ))
echo -e "${private_total} + ${shared_total} = ${sum} ${name}"
}
function mainf() {
get_parms "$@" || return 1
validate_parms || return 1
# To ensure temp files not exist.
rm -f ${temp_file_list}
for pid in ${pids} ; do
get_process_mem ${pid} >> ${temp_file_path_1}
done
# This is used to sort results by memory usage.
sort -gr -k 5 ${temp_file_path_1} > ${temp_file_path_2}
# Find duplicates in the process list output and combine them. In such
# cases, adjust the process name by including a (<count>) suffix. In the
# following example of output, 4 instances of "phosphor-hwmon-readd" have
# been combined.
# 974848 + 925696 = 1900544 phosphor-hwmon-readd(4)
for proc_name in $(awk '{print $6}' ${temp_file_path_2} | sort -u) ; do
count=$(awk -v src=${proc_name} '{if ($6==src) {print $6}}' ${temp_file_path_2} | wc -l)
[ "${count}" = "1" ] && count_string="" || count_string="(${count})"
vmsize_in_kb=$(awk -v src=${proc_name} '{if ($6==src) {print $1}}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}')
vmrss_in_kb=$(awk -v src=${proc_name} '{if ($6==src) {print $3}}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}')
total=$(awk '{print $5}' ${temp_file_path_2} | awk '{sum += $1} END {print sum}')
(( sum=vmrss_in_kb+vmsize_in_kb ))
echo -e "${vmsize_in_kb} + ${vmrss_in_kb} = ${sum} \t ${proc_name}${count_string}" >> ${temp_file_path_3}
done
# Sort once more.
sort -gr -k 5 ${temp_file_path_3} > ${temp_file_path_1}
# Read results from temp file and convert it to csv.
csv_line1=""
csv_line2=""
while read line ; do
while read private plus_operator shared equal_sign sum name ; do
(( sum == 0 )) && continue
csv_line1+=",${name}"
csv_line2+=",${sum}"
done <<<${line}
done < ${temp_file_path_1}
# Strip leading commas.
csv_line1="${csv_line1#,}"
csv_line2="${csv_line2#,}"
{ echo "${csv_line1}" ; echo "${csv_line2}" ; } >> ${csv_file_path}
return 0
}
# Main
mainf "${@}"
rc="${?}"
exit "${rc}"