diff --git a/case-lib/hijack.sh b/case-lib/hijack.sh index 4a40ff36..3793a80e 100644 --- a/case-lib/hijack.sh +++ b/case-lib/hijack.sh @@ -16,6 +16,10 @@ function func_exit_handler() func_lib_check_and_disable_pipewire + if [ "$RUN_SOCWATCH" == true ]; then + unload_socwatch + fi + # call trace if [ "$exit_status" -ne 0 ] ; then dloge "Starting ${FUNCNAME[0]}(), exit status=$exit_status, FUNCNAME stack:" diff --git a/case-lib/lib.sh b/case-lib/lib.sh index ef3d78f3..a5b763ca 100644 --- a/case-lib/lib.sh +++ b/case-lib/lib.sh @@ -83,6 +83,10 @@ minvalue() { printf '%d' $(( "$1" < "$2" ? "$1" : "$2" )); } # start_test() { + if [ "$RUN_SOCWATCH" == true ]; then + load_socwatch + fi + if is_subtest; then return 0 fi @@ -1701,3 +1705,45 @@ analyze_mixed_sound() return 1 fi } + +# Generates s 2-channels .mp3 file for testing +# Arguments: +# 1 - output filename +# 2 - duration of the soundfile in seconds +# 3 - number of channels +generate_mp3_file() +{ + mkdir -p "$HOME/Music" + ffmpeg -f lavfi -i "sine=frequency=1000:duration=$2" -ac "$3" "$1" +} + +# Load socwatch and check if module was loaded correctly +load_socwatch() +{ + sudo bash "$SOCWATCH_PATH"/drivers/insmod-socwatch || true + lsmod | grep -q socwatch || die "Socwatch is not loaded" +} + +unload_socwatch() +{ + sudo bash "$SOCWATCH_PATH"/drivers/rmmod-socwatch +} + +# Run any command with socwatch +# Arguments: +# 1 - socwatch output report filename +# 2 - command you want to run with socwatch (with arguments) +run_with_socwatch() +{ + if [ -z "$SOCWATCH_PATH" ]; then + die "SOCWATCH_PATH not set" + fi + + local output_file="$1" + shift + + ( set -x + sudo "$SOCWATCH_PATH"/socwatch -m -n 200 -f cpu-pkg-cstate-res \ + -r json -o "$output_file" -p "$@") || + die "socwatch returned $?" +} diff --git a/test-case/test-compressed-audio.sh b/test-case/test-compressed-audio.sh new file mode 100755 index 00000000..45f5d770 --- /dev/null +++ b/test-case/test-compressed-audio.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +## +## Case Name: test-compressed-audio +## Preconditions: +## - socwatch installed +## - cplay installed +## Description: +## This test verifies if we enter PC10 state when playing MP3 with offload to DSP. +## Case steps: +## 1. Generate MP3 sound for testing. +## 2. Start Socwatch measurement and play MP3 file. +## 3. Analyze Socwatch results. +## Expected results: +## - generated MP3 is played +## - dut stays in the PC10 state for the expected amount of time +## + +# shellcheck source=case-lib/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh + +OPT_NAME['p']='pcm_p' OPT_DESC['p']='compression device for playback. Example: 50' +OPT_HAS_ARG['p']=1 OPT_VAL['p']='' + +OPT_NAME['N']='channels_p' OPT_DESC['N']='channel number for playback.' +OPT_HAS_ARG['N']=1 OPT_VAL['N']='2' + +OPT_NAME['s']='sof-logger' OPT_DESC['s']="Open sof-logger trace the data will store at $LOG_ROOT" +OPT_HAS_ARG['s']=0 OPT_VAL['s']=1 + +OPT_NAME['d']='duration' OPT_DESC['d']='duration time for playing the test sound' +OPT_HAS_ARG['d']=1 OPT_VAL['d']=10 + +OPT_NAME['pc10_per']='pc10_per' OPT_DESC['pc10_per']='pc10 state threshold - percentage of time that should be spent in pc10' +OPT_HAS_ARG['pc10_per']=1 OPT_VAL['pc10_per']=80 + +: "${SOCWATCH_PATH:=$HOME/socwatch}" + +func_opt_parse_option "$@" +setup_kernel_check_point + +pcm_p=${OPT_VAL['p']} +channels_p=${OPT_VAL['N']} +duration=${OPT_VAL['d']} +pc10_threshold=${OPT_VAL['pc10_per']} + +analyze_socwatch_results() +{ + pc_states_file="$LOG_ROOT/pc_states.csv" + touch "$pc_states_file" + results=$(grep "Platform Monitoring Technology CPU Package C-States Residency Summary: Residency" -A 10 < "$socwatch_output".csv) + echo "$results" | tee "$pc_states_file" + + expected_results="{\"PC10.2\":$pc10_threshold}" + + # Analyze if the % of the time spent in given PC state was as expected + if python3 "$SCRIPT_HOME"/tools/analyze-pc-states.py "$pc_states_file" "$expected_results"; then + dlogi "All Package Residency (%) values were as expected" + else + die "Some Package Residency (%) values different from expected!" + fi +} + +# Checks for soundfile needed for test, generates missing ones +prepare_test_soundfile() +{ + if [ ! -f "$audio_filename" ]; then + dlogi "Generating audio file for the test..." + generate_mp3_file "$audio_filename" "$duration" "$channels_p" + fi +} + +check_cplay_command() +{ + dlogi "${play_command[@]}" + "${play_command[@]}" || die "cplay command returned error, socwatch analysis not performed" +} + +run_test() +{ + audio_filename="$HOME/Music/$channels_p-ch-$duration-s.mp3" + prepare_test_soundfile + + socwatch_output="$LOG_ROOT/socwatch-results/socwatch_report" + + play_command=("cplay" "-c" "0" "-d" "$pcm_p" "-I" "MP3" "${audio_filename}" "-v") + check_cplay_command + + run_with_socwatch "$socwatch_output" "${play_command[@]}" + + analyze_socwatch_results +} + +main() +{ + export RUN_SOCWATCH=true + start_test + logger_disabled || func_lib_start_log_collect + run_test +} + +{ + main "$@"; exit "$?" +} diff --git a/tools/analyze-pc-states.py b/tools/analyze-pc-states.py new file mode 100644 index 00000000..6febace4 --- /dev/null +++ b/tools/analyze-pc-states.py @@ -0,0 +1,52 @@ +import sys +import json +import re + +ACCEPTANCE_BUFFER = 8.0 + + +def compare_values(real, expected): + if abs(real-expected) <= ACCEPTANCE_BUFFER: + return True + return False + + +def analyze_pc_states(pc_states_file, expected_results): + pattern = re.compile(r'^PC(\d+)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*$') + # Example lines we want to catch: + # PC0 , 81.09 , 119.51 + # PC2 , 18.91 , 27.87 + failures = 0 + + with open(pc_states_file, encoding="utf-8") as file: + for line in file: + m = pattern.match(line) + if not m: + continue + + pc_state_nr = int(m.group(1)) + pc_state = "PC"+str(pc_state_nr) + value = float(m.group(2)) + expected_value = expected_results.get(pc_state) + if not expected_value: + continue + if not compare_values(value, float(expected_value)): + print(f"Incorrect value: {pc_state} time % was {value}, expected {expected_value}") + failures += 1 + + if failures: + return 1 + return 0 + + +# This script analyzes if the % of the time spent in given PC state was as expected +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Incorrect number of args!") + sys.exit(1) + + pc_states_results_file = sys.argv[1] + pc_states_thresholds = json.loads(sys.argv[2]) + + result = analyze_pc_states(pc_states_results_file, pc_states_thresholds) + sys.exit(result)