You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.9 KiB
191 lines
5.9 KiB
#!/bin/sh |
|
|
|
# Try to find out kernel modules with large total memory allocation during loading. |
|
# For large slab allocation, it will fall into buddy, also not trace "mm_page_free" |
|
# considering large free is quite rare for module_init, thus saving tons of events |
|
# to avoid trace data overwritten. |
|
# |
|
# Therefore, tracing "mm_page_alloc"alone should be enough for the purpose. |
|
|
|
# "sys/kernel/tracing" has the priority if exists. |
|
get_trace_base() { |
|
# trace access through debugfs would be obsolete if "/sys/kernel/tracing" is available. |
|
if [ -d "/sys/kernel/tracing" ]; then |
|
echo "/sys/kernel" |
|
else |
|
echo "/sys/kernel/debug" |
|
fi |
|
} |
|
|
|
# We want to enable these trace events. |
|
get_want_events() { |
|
echo "module:module_put module:module_load kmem:mm_page_alloc" |
|
} |
|
|
|
get_event_filter() { |
|
echo "comm == systemd-udevd || comm == modprobe || comm == insmod" |
|
} |
|
|
|
is_trace_ready() { |
|
local trace_base want_events current_events |
|
|
|
trace_base=$(get_trace_base) |
|
! [ -f "$trace_base/tracing/trace" ] && return 1 |
|
|
|
[ "$(cat $trace_base/tracing/tracing_on)" -eq 0 ] && return 1 |
|
|
|
# Also check if trace events were properly setup. |
|
want_events=$(get_want_events) |
|
current_events=$(echo $(cat $trace_base/tracing/set_event)) |
|
[ "$current_events" != "$want_events" ] && return 1 |
|
|
|
return 0 |
|
} |
|
|
|
prepare_trace() { |
|
local trace_base |
|
|
|
trace_base=$(get_trace_base) |
|
# old debugfs interface case. |
|
if ! [ -d "$trace_base/tracing" ]; then |
|
mount none -t debugfs $trace_base |
|
# new tracefs interface case. |
|
elif ! [ -f "$trace_base/tracing/trace" ]; then |
|
mount none -t tracefs "$trace_base/tracing" |
|
fi |
|
|
|
if ! [ -f "$trace_base/tracing/trace" ]; then |
|
echo "WARN: Mount trace failed for kernel module memory analyzing." |
|
return 1 |
|
fi |
|
|
|
# Active all the wanted trace events. |
|
echo "$(get_want_events)" > $trace_base/tracing/set_event |
|
|
|
# There are three kinds of known applications for module loading: |
|
# "systemd-udevd", "modprobe" and "insmod". |
|
# Set them as the global events filter. |
|
# NOTE: Some kernel may not support this format of filter, anyway |
|
# the operation will fail and it doesn't matter. |
|
echo "$(get_event_filter)" > $trace_base/tracing/events/kmem/filter 2>&1 |
|
echo "$(get_event_filter)" > $trace_base/tracing/events/module/filter 2>&1 |
|
|
|
# Set the number of comm-pid if supported. |
|
if [ -f "$trace_base/tracing/saved_cmdlines_size" ]; then |
|
# Thanks to filters, 4096 is big enough(also well supported). |
|
echo 4096 > $trace_base/tracing/saved_cmdlines_size |
|
fi |
|
|
|
# Enable and clear trace data for the first time. |
|
echo 1 > $trace_base/tracing/tracing_on |
|
echo > $trace_base/tracing/trace |
|
echo "Prepare trace success." |
|
return 0 |
|
} |
|
|
|
order_to_pages() |
|
{ |
|
local pages=1 |
|
local order=$1 |
|
|
|
while [ "$order" != 0 ]; do |
|
order=$((order-1)) |
|
pages=$(($pages*2)) |
|
done |
|
|
|
echo $pages |
|
} |
|
|
|
parse_trace_data() { |
|
local module_name tmp_eval pages |
|
|
|
cat "$(get_trace_base)/tracing/trace" | while read pid cpu flags ts function args |
|
do |
|
# Skip comment lines |
|
if [ "$pid" = "#" ]; then |
|
continue |
|
fi |
|
|
|
pid=${pid##*-} |
|
function=${function%:} |
|
if [ "$function" = "module_load" ]; then |
|
# One module is being loaded, save the task pid for tracking. |
|
# Remove the trailing after whitespace, there may be the module flags. |
|
module_name=${args%% *} |
|
# Mark current_module to track the task. |
|
eval current_module_$pid="$module_name" |
|
tmp_eval=$(eval echo '${module_loaded_'${module_name}'}') |
|
if [ -n "$tmp_eval" ]; then |
|
echo "WARN: \"$module_name\" was loaded multiple times!" |
|
fi |
|
eval unset module_loaded_$module_name |
|
eval nr_alloc_pages_$module_name=0 |
|
continue |
|
fi |
|
|
|
module_name=$(eval echo '${current_module_'${pid}'}') |
|
if [ -z "$module_name" ]; then |
|
continue |
|
fi |
|
|
|
# Once we get here, the task is being tracked(is loading a module). |
|
if [ "$function" = "module_put" ]; then |
|
# Mark the module as loaded when the first module_put event happens after module_load. |
|
tmp_eval=$(eval echo '${nr_alloc_pages_'${module_name}'}') |
|
echo "$tmp_eval pages consumed by \"$module_name\"" |
|
eval module_loaded_$module_name=1 |
|
# Module loading finished, so untrack the task. |
|
eval unset current_module_$pid |
|
eval unset nr_alloc_pages_$module_name |
|
continue |
|
fi |
|
|
|
if [ "$function" = "mm_page_alloc" ]; then |
|
# Get order first, then convert to actual pages. |
|
pages=$(echo $args | sed -e 's/.*order=\([0-9]*\) .*/\1/') |
|
pages=$(order_to_pages "$pages") |
|
tmp_eval=$(eval echo '${nr_alloc_pages_'${module_name}'}') |
|
eval nr_alloc_pages_$module_name="$(($tmp_eval+$pages))" |
|
fi |
|
done |
|
} |
|
|
|
cleanup_trace() { |
|
local trace_base |
|
|
|
if is_trace_ready; then |
|
trace_base=$(get_trace_base) |
|
echo 0 > $trace_base/tracing/tracing_on |
|
echo > $trace_base/tracing/trace |
|
echo > $trace_base/tracing/set_event |
|
echo 0 > $trace_base/tracing/events/kmem/filter |
|
echo 0 > $trace_base/tracing/events/module/filter |
|
fi |
|
} |
|
|
|
show_usage() { |
|
echo "Find out kernel modules with large memory consumption during loading based on trace." |
|
echo "Usage:" |
|
echo "1) run it first to setup trace." |
|
echo "2) run again to parse the trace data if any." |
|
echo "3) run with \"--cleanup\" option to cleanup trace after use." |
|
} |
|
|
|
if [ "$1" = "--help" ]; then |
|
show_usage |
|
exit 0 |
|
fi |
|
|
|
if [ "$1" = "--cleanup" ]; then |
|
cleanup_trace |
|
exit 0 |
|
fi |
|
|
|
if is_trace_ready ; then |
|
echo "tracekomem - Rough memory consumption by loading kernel modules (larger value with better accuracy)" |
|
parse_trace_data |
|
else |
|
prepare_trace |
|
fi |
|
|
|
exit $?
|
|
|