| #!/bin/bash | |
| # perf timechart --tui (ttimechart.py) test | |
| # SPDX-License-Identifier: GPL-2.0 | |
| set -e | |
| shelldir=$(dirname "$0") | |
| # shellcheck source=lib/setup_python.sh | |
| . "${shelldir}"/lib/setup_python.sh | |
| if ! perf check feature -q libtraceevent; then | |
| echo "Skipping test, libtraceevent is disabled" | |
| exit 2 | |
| fi | |
| if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then | |
| echo "Skipping test, perf python module not found" | |
| exit 2 | |
| fi | |
| if ! "$PYTHON" -c 'import textual' > /dev/null 2>&1; then | |
| echo "Skipping test, python textual library not found" | |
| exit 2 | |
| fi | |
| err=0 | |
| perfdata= | |
| output= | |
| clockdata= | |
| cleanup() { | |
| [ -n "${perfdata}" ] && rm -f "${perfdata}"* | |
| [ -n "${clockdata}" ] && rm -f "${clockdata}"* | |
| rm -f "${output}" | |
| trap - EXIT TERM INT | |
| } | |
| trap_cleanup() { | |
| echo "Unexpected signal in ${FUNCNAME[1]}" | |
| cleanup | |
| exit 1 | |
| } | |
| trap trap_cleanup EXIT TERM INT | |
| perfdata=$(mktemp /tmp/__perf_ttimechart_test.perf.data.XXXXX) | |
| output=$(mktemp /tmp/__perf_ttimechart_test.output.XXXXX) | |
| clockdata=$(mktemp /tmp/__perf_ttimechart_test.clock.data.XXXXX) | |
| workload() { | |
| for _ in 1 2 3 4 5; do | |
| sleep 0.01 | |
| done | |
| } | |
| record() { | |
| # perf timechart record is system wide and so may need privileges, | |
| # fall back to recording just the workload's scheduler events. | |
| if perf timechart record -o "${perfdata}" -- \ | |
| bash -c "$(declare -f workload); workload" > /dev/null 2>&1; then | |
| return 0 | |
| fi | |
| if perf record -R -c 1 -e sched:sched_switch -e sched:sched_wakeup \ | |
| -o "${perfdata}" -- bash -c "$(declare -f workload); workload" \ | |
| > /dev/null 2>&1; then | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| test_plumbing() { | |
| echo "perf timechart --tui plumbing test" | |
| # A missing input file should be reported by the script launched | |
| # through perf script, this doesn't need a terminal. | |
| if perf timechart --tui -i "${perfdata}.missing" > "${output}" 2>&1; then | |
| echo "perf timechart --tui plumbing test [Failed: missing file not an error]" | |
| err=1 | |
| return | |
| fi | |
| if ! grep -q "not found" "${output}"; then | |
| echo "perf timechart --tui plumbing test [Failed: script not launched]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| echo "perf timechart --tui plumbing test [Success]" | |
| } | |
| test_no_events() { | |
| echo "ttimechart no events test" | |
| if ! perf record -e task-clock -o "${clockdata}" -- true > /dev/null 2>&1; then | |
| echo "ttimechart no events test [Skipped: perf record failed]" | |
| return | |
| fi | |
| if perf script ttimechart -i "${clockdata}" --dump > "${output}" 2>&1; then | |
| echo "ttimechart no events test [Failed: expected an error]" | |
| err=1 | |
| return | |
| fi | |
| if ! grep -q "no scheduler, power or I/O events" "${output}"; then | |
| echo "ttimechart no events test [Failed: unexpected error message]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| # The same should be reported when reading the data from a pipe. | |
| if perf record -e task-clock -o - -- true 2> /dev/null | \ | |
| perf script ttimechart -i - --dump > "${output}" 2>&1; then | |
| echo "ttimechart no events test [Failed: expected an error from a pipe]" | |
| err=1 | |
| return | |
| fi | |
| if ! grep -q "no scheduler, power or I/O events" "${output}"; then | |
| echo "ttimechart no events test [Failed: unexpected error message from a pipe]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| echo "ttimechart no events test [Success]" | |
| } | |
| test_dump() { | |
| echo "ttimechart dump test" | |
| if ! perf script ttimechart -i "${perfdata}" --dump > "${output}" 2>&1; then | |
| echo "ttimechart dump test [Failed: script failed]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| if ! grep -q "^Duration: .*sched events: [1-9]" "${output}" || | |
| ! grep -q "^CPU [0-9]*: busy" "${output}" || | |
| ! grep -q "^Task .*Running .*Waiting .*Blocked" "${output}" || | |
| ! grep -q "sleep" "${output}"; then | |
| echo "ttimechart dump test [Failed: unexpected output]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| # Tasks only shouldn't show CPUs, power only shouldn't show tasks. | |
| perf script ttimechart -i "${perfdata}" --dump -T > "${output}" 2>&1 || true | |
| if grep -q "^CPU [0-9]*: " "${output}" || | |
| ! grep -q "^Task .*Running .*Waiting .*Blocked" "${output}"; then | |
| echo "ttimechart dump test [Failed: -T output]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| perf script ttimechart -i "${perfdata}" --dump -P > "${output}" 2>&1 || true | |
| if grep -q "^Task .*Running .*Waiting .*Blocked" "${output}" || | |
| ! grep -q "^CPU [0-9]*: " "${output}"; then | |
| echo "ttimechart dump test [Failed: -P output]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| # Filtering on the workload shouldn't show other tasks. | |
| perf script ttimechart -i "${perfdata}" --dump -p sleep > "${output}" 2>&1 || true | |
| if ! grep -q "^sleep " "${output}" || grep -q "^bash " "${output}"; then | |
| echo "ttimechart dump test [Failed: -p output]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| echo "ttimechart dump test [Success]" | |
| } | |
| test_headless_ui() { | |
| echo "ttimechart headless UI test" | |
| # Drive the textual app without a terminal, exercising the views and | |
| # key bindings. | |
| if ! "$PYTHON" - "${perfdata}" > "${output}" 2>&1 <<'EOF' | |
| import asyncio | |
| import sys | |
| import ttimechart as tc | |
| async def run() -> None: | |
| # The app starts before the data is loaded in a background thread. | |
| app = tc.TimechartApp(sys.argv[1], False, False, []) | |
| async with app.run_test(size=(120, 40)) as pilot: | |
| for _ in range(600): | |
| if app.loaded: | |
| break | |
| await pilot.pause(0.1) | |
| if not app.loaded: | |
| raise RuntimeError("Timed out loading data") | |
| await pilot.pause() | |
| tabs = app.query_one(tc.TabbedContent) | |
| for tab in ["cpus", "tasks", "summary"]: | |
| tabs.active = tab | |
| await pilot.pause() | |
| await pilot.press("down", "plus", "plus", "right", "n", "p", "minus", | |
| "shift+right", "shift+left", "s", "w", "0") | |
| await pilot.pause() | |
| tabs.active = "summary" | |
| await pilot.pause() | |
| await pilot.press("enter") | |
| await pilot.pause() | |
| details = str(app.query_one("#details", tc.Static).render()) | |
| if "Cursor" not in details: | |
| raise RuntimeError(f"Unexpected details: {details}") | |
| print("headless UI ok") | |
| asyncio.run(run()) | |
| EOF | |
| then | |
| echo "ttimechart headless UI test [Failed: exception]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| if ! grep -q "headless UI ok" "${output}"; then | |
| echo "ttimechart headless UI test [Failed: unexpected output]" | |
| cat "${output}" | |
| err=1 | |
| return | |
| fi | |
| echo "ttimechart headless UI test [Success]" | |
| } | |
| test_plumbing | |
| test_no_events | |
| if ! record; then | |
| echo "Skipping remaining tests, failed to record scheduler events (permissions?)" | |
| if [ $err -eq 0 ]; then | |
| err=2 | |
| fi | |
| cleanup | |
| exit $err | |
| fi | |
| test_dump | |
| test_headless_ui | |
| cleanup | |
| exit $err |