From aa8e581b253daaec2d20953a5d98153e823972dc Mon Sep 17 00:00:00 2001 From: Emilia Kurdybelska Date: Thu, 12 Feb 2026 11:00:14 +0100 Subject: [PATCH 1/2] test: add compressed audio with DSP offload test Signed-off-by: Emilia Kurdybelska --- case-lib/hijack.sh | 4 + case-lib/lib.sh | 43 ++++++++ test-case/residency-time-test.sh | 9 +- test-case/test-cplay.sh | 168 +++++++++++++++++++++++++++++++ tools/analyze-pc-states.py | 46 +++++++++ 5 files changed, 263 insertions(+), 7 deletions(-) create mode 100644 test-case/test-cplay.sh create mode 100644 tools/analyze-pc-states.py 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..982be4e8 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,42 @@ analyze_mixed_sound() return 1 fi } + +# Generates 20s .mp3 file for testing +# Arguments: 1 - output filename +generate_mp3_file() +{ + ffmpeg -f lavfi -i "sine=frequency=1000:duration=20" "$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..97560a57 --- /dev/null +++ b/test-case/test-cplay.sh @@ -0,0 +1,168 @@ +#!/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_for_PC10_state() +{ + pc10_count=$(awk '/Package C-State Summary: Entry Counts/{f=1; next} f && /PC10/{print $3; exit}' "$socwatch_output".csv) + if [ -z "$pc10_count" ]; then + die "PC10 State not achieved" + fi + dlogi "Entered into PC10 State $pc10_count times" + + pc10_per=$(awk '/Package C-State Summary: Residency/{f=1; next} f && /PC10/{print $3; exit}' "$socwatch_output".csv) + pc10_time=$(awk '/Package C-State Summary: Residency/{f=1; next} f && /PC10/{print $5; exit}' "$socwatch_output".csv) + dlogi "Spent $pc10_time ms ($pc10_per %) in PC10 State" + + json_str=$( jq -n \ + --arg id "$i" \ + --arg cnt "$pc10_count" \ + --arg time "$pc10_time" \ + --arg per "$pc10_per" \ + '{$id: {pc10_entires_count: $cnt, time_ms: $time, time_percentage: $per}}' ) + + results=$(jq --slurp 'add' <(echo "$results") <(echo "$json_str")) +} + +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() +{ + mkdir -p "$HOME/Music" + 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${pcm_p} -d ${duration} ${audio_filename}" + play_command=(aplay -Dplug${pcm_p} -d ${duration} /dev/zero) + 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) From 70150f4aae92263441e104e1c5cce3ed861133b1 Mon Sep 17 00:00:00 2001 From: Emilia Kurdybelska Date: Thu, 19 Feb 2026 14:54:56 +0100 Subject: [PATCH 2/2] test-compressed-audio: add test Add test that plays mp3 audio using cplay, and check PC states with Socwatch Signed-off-by: Emilia Kurdybelska --- case-lib/lib.sh | 10 +- test-case/test-compressed-audio.sh | 95 ++++++++++++++++ test-case/test-cplay.sh | 168 ----------------------------- 3 files changed, 102 insertions(+), 171 deletions(-) create mode 100644 test-case/test-compressed-audio.sh delete mode 100644 test-case/test-cplay.sh diff --git a/case-lib/lib.sh b/case-lib/lib.sh index 982be4e8..b8cf7301 100644 --- a/case-lib/lib.sh +++ b/case-lib/lib.sh @@ -1604,11 +1604,15 @@ analyze_mixed_sound() fi } -# Generates 20s .mp3 file for testing -# Arguments: 1 - output filename +# 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() { - ffmpeg -f lavfi -i "sine=frequency=1000:duration=20" "$1" + 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 diff --git a/test-case/test-compressed-audio.sh b/test-case/test-compressed-audio.sh new file mode 100644 index 00000000..f648a263 --- /dev/null +++ b/test-case/test-compressed-audio.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +## +## Case Name: test-compressed-audio +## +## Preconditions: +## TODO +## +## Description: +## TODO +## +## Case step: +## TODO +## +## Expect result: +## TODO +## + +# 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 + +: "${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']} +channels_p=${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 +} + +# 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 +} + +run_test() +{ + audio_filename="$HOME/Music/$channels_p-ch-$duration-s.mp3" + prepare_test_soundfile + + socwatch_output="$LOG_ROOT/socwatch-results/socwatch_report" + + # cplay -c 0 -d "$pcm_p" -I MP3 "$audio_filename" -v + + play_command=("cplay" "-c" "0" "-d" "$pcm_p" "-I" "MP3" "${audio_filename}" "-v") + 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/test-case/test-cplay.sh b/test-case/test-cplay.sh deleted file mode 100644 index 97560a57..00000000 --- a/test-case/test-cplay.sh +++ /dev/null @@ -1,168 +0,0 @@ -#!/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_for_PC10_state() -{ - pc10_count=$(awk '/Package C-State Summary: Entry Counts/{f=1; next} f && /PC10/{print $3; exit}' "$socwatch_output".csv) - if [ -z "$pc10_count" ]; then - die "PC10 State not achieved" - fi - dlogi "Entered into PC10 State $pc10_count times" - - pc10_per=$(awk '/Package C-State Summary: Residency/{f=1; next} f && /PC10/{print $3; exit}' "$socwatch_output".csv) - pc10_time=$(awk '/Package C-State Summary: Residency/{f=1; next} f && /PC10/{print $5; exit}' "$socwatch_output".csv) - dlogi "Spent $pc10_time ms ($pc10_per %) in PC10 State" - - json_str=$( jq -n \ - --arg id "$i" \ - --arg cnt "$pc10_count" \ - --arg time "$pc10_time" \ - --arg per "$pc10_per" \ - '{$id: {pc10_entires_count: $cnt, time_ms: $time, time_percentage: $per}}' ) - - results=$(jq --slurp 'add' <(echo "$results") <(echo "$json_str")) -} - -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() -{ - mkdir -p "$HOME/Music" - 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${pcm_p} -d ${duration} ${audio_filename}" - play_command=(aplay -Dplug${pcm_p} -d ${duration} /dev/zero) - 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 "$?" -}