summaryrefslogtreecommitdiff
path: root/tools/perf/tests/shell/test_ttimechart_python.sh
blob: 87b91ed8fc167d2d8d810afa942c595e56faada6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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