diff --git a/case-lib/hijack.sh b/case-lib/hijack.sh index 6f14e954..3090af26 100644 --- a/case-lib/hijack.sh +++ b/case-lib/hijack.sh @@ -14,6 +14,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 15d122ba..9491b3cd 100644 --- a/case-lib/lib.sh +++ b/case-lib/lib.sh @@ -87,6 +87,10 @@ start_test() func_lib_enable_pipewire fi + if [ "$RUN_SOCWATCH" == true ]; then + load_socwatch + fi + if is_subtest; then return 0 fi @@ -1599,3 +1603,43 @@ analyze_mixed_sound() return 1 fi } + +# Generates 20s .mp3 file for testing +# Arguments: 1 - output filename +generate_mp3_file() +{ + mkdir -p "$HOME/Music" + ffmpeg -f lavfi -i "sine=frequency=1000:duration=20" -ac 2 "$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 -f sys -f cpu -f cpu-hw -f pcie \ + -f hw-cpu-cstate -f pcd-slps0 -f tcss-state -f tcss -f pcie-lpm -n 200 \ + -r json -o "$output_file" -p "$@") || + die "socwatch returned $?" +} diff --git a/test-case/residency-time-test.sh b/test-case/residency-time-test.sh index 71f5738b..b3837052 100755 --- a/test-case/residency-time-test.sh +++ b/test-case/residency-time-test.sh @@ -137,10 +137,6 @@ load_modules() run_socwatch_tests() { - # load socwatch module, if the module is loaded, go ahead with the testing - sudo bash "$SOCWATCH_PATH"/drivers/insmod-socwatch || true - check_socwatch_module_loaded || die "socwatch module not loaded" - # Create a dir for all socwatch reports mkdir "$LOG_ROOT/socwatch-results" pc10_results_file="$LOG_ROOT/socwatch-results/pc10_results.json" @@ -159,15 +155,14 @@ run_socwatch_tests() cd "$LOG_ROOT" tar -zcvf socwatch-results.tar.gz socwatch-results/ rm -rf "$LOG_ROOT/socwatch-results/" - - # unload socwatch module - sudo bash "$SOCWATCH_PATH"/drivers/rmmod-socwatch } main() { unload_modules + load_socwatch run_socwatch_tests + unload_socwatch load_modules } diff --git a/test-case/test-cplay.sh b/test-case/test-cplay.sh new file mode 100644 index 00000000..710e8464 --- /dev/null +++ b/test-case/test-cplay.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +## +## Case Name: check alsabat +## +## Preconditions: +## This test case requires physical loopback between playback and capture. +## playback <=====> capture +## nocodec : no need to use hw loopback cable, It support DSP loopback by quirk +## +## Description: +## Run two alsabat instances concurrently, one on each specified PCM: playback +## and capture. +## +## Warning: as of January 2024, "man alsabat" is incomplete and +## documents only the "single instance" mode where a single alsabat +## process performs both playback and capture. +## +## Case step: +## 1. Specify the pcm IDs for playback and catpure +## 3. run alsabat test +## +## Expect result: +## The return value of alsabat is 0 +## + +# remove the existing alsabat wav files +rm -f /tmp/bat.wav.* + +# shellcheck source=case-lib/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh + +OPT_NAME['p']='pcm_p' OPT_DESC['p']='pcm for playback. Example: hw:0,0' +OPT_HAS_ARG['p']=1 OPT_VAL['p']='' + +OPT_NAME['C']='channel_c' OPT_DESC['C']='channel number for capture.' +OPT_HAS_ARG['C']=1 OPT_VAL['C']='1' + +OPT_NAME['N']='channel_p' OPT_DESC['N']='channel number for playback.' +OPT_HAS_ARG['N']=1 OPT_VAL['N']='2' + +OPT_NAME['r']='rate' OPT_DESC['r']='sample rate' +OPT_HAS_ARG['r']=1 OPT_VAL['r']=48000 + +OPT_NAME['c']='pcm_c' OPT_DESC['c']='pcm for capture. Example: hw:1,0' +OPT_HAS_ARG['c']=1 OPT_VAL['c']='' + +OPT_NAME['f']='format' OPT_DESC['f']='target format' +OPT_HAS_ARG['f']=1 OPT_VAL['f']="S16_LE" + +OPT_NAME['F']='frequency' OPT_DESC['F']='target frequency' +OPT_HAS_ARG['F']=1 OPT_VAL['F']=821 + +OPT_NAME['k']='sigmak' OPT_DESC['k']='sigma k value' +OPT_HAS_ARG['k']=1 OPT_VAL['k']=2.1 + +OPT_NAME['n']='frames' OPT_DESC['n']='test frames' +OPT_HAS_ARG['n']=1 OPT_VAL['n']=240000 + +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 socwatch to collect the data' +OPT_HAS_ARG['d']=1 OPT_VAL['d']=10 + +: "${SOCWATCH_PATH:=$HOME/socwatch}" +SOCWATCH_VERSION=$(sudo "$SOCWATCH_PATH"/socwatch --version | grep Version) + +func_opt_parse_option "$@" +setup_kernel_check_point + +pcm_p=${OPT_VAL['p']} +pcm_c=${OPT_VAL['c']} +rate=${OPT_VAL['r']} +channel_c=${OPT_VAL['C']} +channel_p=${OPT_VAL['N']} +format=${OPT_VAL['f']} +frequency=${OPT_VAL['F']} +sigmak=${OPT_VAL['k']} +frames=${OPT_VAL['n']} +duration=${OPT_VAL['d']} + +analyze_socwatch_results() +{ + pc_states_file="$LOG_ROOT/pc_states.csv" + touch "$pc_states_file" + results=$(cat "$socwatch_output".csv | grep "Platform Monitoring Technology CPU Package C-States Residency Summary: Residency" -A 10) + echo "$results" | tee "$pc_states_file" + + expected_results='{"PC0":12.00, "PC2":88, "PC6.1":0, "PC6.2":11, "PC10.1":2, "PC10.2":72, "PC10.3":0}' + + # 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 +} + +check_the_pcms() +{ + aplay "-Dplug${pcm_p}" -d 1 /dev/zero -q || die "Failed to play on PCM: ${pcm_p}" + arecord "-Dplug${pcm_c}" -d 1 /dev/null -q || die "Failed to capture on PCM: ${pcm_c}" +} + +# Checks for soundfile needed for test, generates missing ones +prepare_test_soundfile() +{ + if [ ! -f "$audio_filename" ]; then + generate_mp3_file "$audio_filename" + fi +} + +run_test() +{ + check_the_pcms + audio_filename="$HOME/Music/test.mp3" + prepare_test_soundfile + + socwatch_output="$LOG_ROOT/socwatch-results/socwatch_report" + + play_command="cplay -d 0 -c 50 ${audio_filename}" + run_with_socwatch "$socwatch_output" "${play_command[@]}" + + analyze_socwatch_results +} + +main() +{ + export RUN_SOCWATCH=true + start_test + # if [ "$pcm_p" = "" ]||[ "$pcm_c" = "" ]; + # then + # dloge "No playback or capture PCM specified." + # exit 2 + # fi + 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..6b12dc3c --- /dev/null +++ b/tools/analyze-pc-states.py @@ -0,0 +1,46 @@ +import csv +import sys +import json +import re + +ACCEPTANCE_BUFFER = 2.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*$') + failures = 0 + + with open(pc_states_file) as file: + for line in file: + m = pattern.match(line) + if m: + pc_state_nr = int(m.group(1)) + pc_state = "PC"+str(pc_state_nr) + value = float(m.group(2)) + expected_value = float(expected_results.get(pc_state)) + if not expected_value: + continue + if not compare_values(value, expected_value): + print(f"Incorrect value: {pc_state} time % was {value}, expected {expected_value}") + failures += 1 + + return 0 if failures == 0 else 1 + + +# 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)