From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f198.google.com (mail-dy1-f198.google.com [74.125.82.198]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8ABEA51596D for ; Fri, 2 Oct 2026 18:27:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.198 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790965630; cv=none; b=OpiKnOUfjiRF7nTWxrhkCadvMsa920uiQWXsYXpIucXu8mKOZ1/MlOcsJ67HRxg7nzUk5g2GKskAparN6ygbd/FqkEgGMSBTtDOFVtAmPOX3vnIRab+1ImhkXNYu67Q5XWR7VyFHJZ3zWWTy6Y9m1HUedAoTFhTwU7odoEqDQuU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790965630; c=relaxed/simple; bh=VjbeaIMkDGkkC/gGn3LkmJGz5W0OV7OTXY8pLZMTifw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=J4RYu/0SHlMGsEH40tvnH1HcVIj9xt92xeZ89I0TIErVOX0HcGIiI+wzl8xJBkaCQgQQVaLct16lS2dBnoqwhZ+rtPqs/OdQTxqUqU6EiKkc/A3VCNNshFaITyRToSODLtIDo+I1T5BVvAyTlMfzYppQnkuie7IJF5p0okO0sto= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=eJriWhDh; arc=none smtp.client-ip=74.125.82.198 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="eJriWhDh" Received: by mail-dy1-f198.google.com with SMTP id 5a478bee46e88-313d1015161so2623640eec.1 for ; Fri, 02 Oct 2026 11:27:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1790965627; x=1791570427; darn=vger.kernel.org; h=content-type:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=UmwR1WzFCYPEGeSyAT5ZyeChuVuCW43YZnOiA3h4KMw=; b=eJriWhDh9xXBzRVzPZxJiSDTTq/l4dCA0upxAYTp01dHzQu4HXRhTxQwaPlI43lrGR m4hGp0wg1INQCr6E4zi6B7lou9cRCTdxpXFNtABk6jmdO9TGv4TjmdivXO4/UpHHlhys 29WVJlF/dzSuNC78X1LQoRvDD5cg6R+VFYKFl36saMTCM8rGrUHXb0XhPg/7XPcNOmh9 r0iquYQ9Zi1ipsHUkAfFvAxDe3hEQg/4LnewR2RE/vzZd52cDwNb2Bz0n6QU9gRfMY+f +EIPYYod9Sday4GWrWqWzjM5mDri2w3175HUfVy7OgOXFXEKA1jwrzL9T4TC5fR9Ctq6 N6QQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790965627; x=1791570427; h=content-type:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=UmwR1WzFCYPEGeSyAT5ZyeChuVuCW43YZnOiA3h4KMw=; b=g5MoLQzb3CnG7E5frvJ6kdPLTVbPxYY5telAudid4Zi17hw2R5E+cLgxvHRKPL6DCk C6fpIpe5fE+ky2gVnpC7uHatDgajpu1TJeIdd71ltju9CTtXbjNKz/SGeBvxkITKjTgW yox8UAcIVNEMMwdYv9PjlUkxvO/dATKf8rGbjMb/lzMLhtPrGnsvj3RErXEnjDQp+2dz GGkysXWv+XrM8wWuasO22PwONlNw7yvyEuDG728uWNMEVfnSwWdhNHCqYv6S77orIO5r MYm2AP9Cj4Fwsm1QTxEYy2rSouXToLpEtp9HnpEhBx9eodfsZs0qwPOpw+/OUdZD0g67 iR3w== X-Forwarded-Encrypted: i=1; AKwUvByD9haXifC58J7AdPjtFhN/DsWf7NUm/37fH8IvYJkYkQlc0l89ttnEOa1cGG4ZHAkF0t9OpY1lrVAd038=@vger.kernel.org X-Gm-Message-State: AFuF++lxAwjaMeMiR1oF2kKemC3p12blRphQ8cLtxAGKwLQb9I6PmzOs +edo+ftwEdyp3b92Is1YV/qhZCwRKtjQu7ayo9gZMEJX+ypYwtcgnNbDLM4d5UN+ir9lJ5wAT2p WrdaZmfWQJQ== X-Received: from dlbdt14.prod.google.com ([2002:a05:7022:258e:b0:144:bcd9:aa06]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:5e07:b0:149:f2b3:ef78 with SMTP id a92af1059eb24-14f5c9d88bdmr3468787c88.32.1790965626860; Fri, 02 Oct 2026 11:27:06 -0700 (PDT) Date: Fri, 2 Oct 2026 11:26:20 -0700 In-Reply-To: <20261002182624.3259797-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20261002182624.3259797-1-irogers@google.com> X-Mailer: git-send-email 2.56.0.rc1.315.gc6ed9934b7-goog Message-ID: <20261002182624.3259797-12-irogers@google.com> Subject: [PATCH v1 11/13] perf test: Add a test for perf timechart --tui From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Jiri Olsa , Ian Rogers , Adrian Hunter , James Clark , Thomas Falcon , Alice Rogers , Changbin Du , Tengda Wu , tanze , Athira Rajeev , Dapeng Mi , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org Content-Type: text/plain; charset="UTF-8" From: Alice Rogers Test launching ttimechart through perf timechart --tui, the error when there are no scheduler events, the --dump output with the -P, -T and -p options, and drive the textual app headless exercising the views and key bindings. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Alice Rogers Co-developed-by: Ian Rogers Signed-off-by: Ian Rogers --- .../tests/shell/test_ttimechart_python.sh | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100755 tools/perf/tests/shell/test_ttimechart_python.sh diff --git a/tools/perf/tests/shell/test_ttimechart_python.sh b/tools/perf/tests/shell/test_ttimechart_python.sh new file mode 100755 index 000000000000..87b91ed8fc16 --- /dev/null +++ b/tools/perf/tests/shell/test_ttimechart_python.sh @@ -0,0 +1,237 @@ +#!/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 -- 2.56.0.rc1.315.gc6ed9934b7-goog