1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
KW_LIB_DIR='src'
. "${KW_LIB_DIR}/lib/kw_include.sh" --source-only
include "${KW_LIB_DIR}/lib/kwlib.sh"
declare -gA options_values
function profiler_main()
{
local list_of_csv_filepaths
parse_profiler_options "$@"
if [[ "$?" != 0 ]]; then
complain "Invalid option: ${options_values['ERROR']}"
profiler_help
return 22 # EINVAL
fi
list_of_csv_filepaths=$(find "${options_values['TARGET_DIRECTORY']}" -name '*.csv')
if [[ -n "${options_values['FULL']}" ]]; then
declare -ga full_profile
process_csv_files "$list_of_csv_filepaths" 'full'
display_full_profile
fi
if [[ -n "${options_values['SUMMARY']}" ]]; then
declare -gA summary
declare -ga functions_stack
process_csv_files "$list_of_csv_filepaths" 'summary'
display_summary
fi
}
# This function processes a set of CSV files and sets data representing a type of
# execution profile. It assumes that the CSV files were generated by kw tracing
# and follow the filename pattern `<thread_number>.csv`, in which the lower the
# number, the earlier the thread was created (number 0 is the main thread).
#
# @list_of_csv_filepaths: String containing all CSV filepaths separated by newline
# @type_of_profile: String defining type of profile to be processed
function process_csv_files()
{
local list_of_csv_filepaths="$1"
local type_of_profile="$2"
local timestamp_difference
local target_function
local current_summary
local timestamp_field
local last_timestamp
local function_field
local thread_number
local action_field
local indentation
local stack_index
type_of_profile="${type_of_profile:-'full'}"
while IFS= read -r csv_filepath; do
thread_number=$(basename "$csv_filepath" | sed -e 's/\.csv//')
stack_index=-1
while IFS=$'\n' read -r line; do
action_field=$(printf '%s' "$line" | cut --delimiter=',' -f1)
function_field=$(printf '%s' "$line" | cut --delimiter=',' -f2)
timestamp_field=$(printf '%s' "$line" | cut --delimiter=',' -f3)
if [[ "$type_of_profile" == 'full' && -n "$last_timestamp" ]]; then
timestamp_difference="$((timestamp_field - last_timestamp))"
full_profile["$thread_number"]+="${indentation}$(bc <<< "scale=3; ${timestamp_difference} / (10^6)") milliseconds"$'\n'
elif [[ "$type_of_profile" == 'summary' && "$stack_index" != -1 ]]; then
timestamp_difference="$((timestamp_field - last_timestamp))"
target_function="${functions_stack["$stack_index"]}"
current_summary="${summary["$target_function"]}"
summary["$target_function"]="$((current_summary + timestamp_difference))"
fi
if [[ "$action_field" == 'entry' ]]; then
case "$type_of_profile" in
'full')
full_profile["$thread_number"]+="${indentation}--> ${function_field}"$'\n'
indentation+=' '
;;
'summary')
functions_stack["$((++stack_index))"]="$function_field"
;;
esac
elif [[ "$action_field" == 'return' ]]; then
case "$type_of_profile" in
'full')
indentation="${indentation::-2}"
full_profile["$thread_number"]+="${indentation}<-- ${function_field}"$'\n'
;;
'summary')
((stack_index--))
;;
esac
else
case "$type_of_profile" in
'full')
full_profile["$thread_number"]+='<'
for i in $(seq 1 "${#indentation}"); do
full_profile["$thread_number"]+='-'
done
full_profile["$thread_number"]+=" ${function_field}"$'\n'
;;
'summary')
stack_index=-1
;;
esac
fi
last_timestamp="$timestamp_field"
done < "$csv_filepath"
last_timestamp=''
indentation=''
done <<< "$list_of_csv_filepaths"
}
# This function outputs the full execution profile processed in
# `process_csv_files` to the standard output.
#
# Return:
# Outputs each thread execution profile in order of creation.
function display_full_profile()
{
local max_thread_number
max_thread_number="${#full_profile[@]}"
((max_thread_number--))
for thread_number in $(seq 0 "$max_thread_number"); do
printf 'Thread nr. %s\n###############\n%s\n' "$thread_number" "${full_profile["$thread_number"]}"
done
}
# This function outputs the summary profile of the execution processed in
# `process_csv_files` to the standard output.
#
# Return:
# Outputs the summary profile of the execution.
function display_summary()
{
for function_name in "${!summary[@]}"; do
printf '%s,%s\n' "$function_name" "${summary["$function_name"]}"
done
}
function parse_profiler_options()
{
local long_options='full,summary,help'
local short_options='h'
options="$(kw_parse "$short_options" "$long_options" "$@")"
if [[ "$?" != 0 ]]; then
options_values['ERROR']="$(kw_parse_get_errors 'kw update' "$short_options" \
"$long_options" "$@")"
return 22 # EINVAL
fi
# Default options values
options_values['FULL']=''
options_values['SUMMARY']=''
options_values['TARGET_DIRECTORY']=''
eval "set -- $options"
while [[ "$#" -gt 0 ]]; do
case "$1" in
--full)
options_values['FULL']=1
shift
;;
--summary)
options_values['SUMMARY']=1
shift
;;
--help | -h)
profiler_help
exit
;;
--)
shift
;;
*)
if [[ ! -d "$1" ]]; then
options_values['ERROR']="Invalid directory path: $1"
return 2 # ENOENT
fi
options_values['TARGET_DIRECTORY']="$1"
shift
;;
esac
done
if [[ -z "${options_values['FULL']}" && -z "${options_values['SUMMARY']}" ]]; then
options_values['ERROR']='kw profiler needs a "--full" or "--summary" option'
return 22 # EINVAL
fi
if [[ -z "${options_values['TARGET_DIRECTORY']}" ]]; then
options_values['ERROR']='kw profiler needs path to tracing dir as parameter'
return 22 # EINVAL
fi
}
function profiler_help()
{
printf '%s\n' 'profiler.sh: Tool for profiling of kw executions' \
' ./scripts/profiler.sh --full <tracing_dir> - Output full profile of execution flow' \
' ./scripts/profiler.sh --summary <tracing_dir> - Output summary of time spent in each function'
}
profiler_main "$@"
|