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
|