diff options
author | Jiri Olsa <jolsa@redhat.com> | 2012-10-31 10:52:47 -0400 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2012-10-31 14:20:58 -0400 |
commit | 52502bf201a85b5b51a384037a002d0b39093df0 (patch) | |
tree | c4d2bcbc4c1f7c10693b132ad70951c06c2cee86 /tools/perf/tests | |
parent | 945aea220bb8f4bb37950549cc0b93bbec24c460 (diff) |
perf tests: Add framework for automated perf_event_attr tests
The idea is run perf session with kidnapping sys_perf_event_open
function. For each sys_perf_event_open call we store the perf_event_attr
data to the file to be checked later against what we expect.
You can run this by:
$ python ./tests/attr.py -d ./tests/attr/ -p ./perf -v
v2 changes:
- preserve errno value in the hook
Signed-off-by: Jiri Olsa <jolsa@redhat.com>
Cc: Arnaldo Carvalho de Melo <acme@ghostprotocols.net>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/20121031145247.GB1027@krava.brq.redhat.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/tests')
-rw-r--r-- | tools/perf/tests/attr.c | 140 | ||||
-rw-r--r-- | tools/perf/tests/attr.py | 313 |
2 files changed, 453 insertions, 0 deletions
diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c new file mode 100644 index 000000000000..55e9a873a5cb --- /dev/null +++ b/tools/perf/tests/attr.c | |||
@@ -0,0 +1,140 @@ | |||
1 | |||
2 | /* | ||
3 | * The struct perf_event_attr test support. | ||
4 | * | ||
5 | * This test is embedded inside into perf directly and is governed | ||
6 | * by the PERF_TEST_ATTR environment variable and hook inside | ||
7 | * sys_perf_event_open function. | ||
8 | * | ||
9 | * The general idea is to store 'struct perf_event_attr' details for | ||
10 | * each event created within single perf command. Each event details | ||
11 | * are stored into separate text file. Once perf command is finished | ||
12 | * these files can be checked for values we expect for command. | ||
13 | * | ||
14 | * Besides 'struct perf_event_attr' values we also store 'fd' and | ||
15 | * 'group_fd' values to allow checking for groups created. | ||
16 | * | ||
17 | * This all is triggered by setting PERF_TEST_ATTR environment variable. | ||
18 | * It must contain name of existing directory with access and write | ||
19 | * permissions. All the event text files are stored there. | ||
20 | */ | ||
21 | |||
22 | #include <stdlib.h> | ||
23 | #include <stdio.h> | ||
24 | #include <inttypes.h> | ||
25 | #include <linux/types.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include "../perf.h" | ||
28 | #include "util.h" | ||
29 | |||
30 | #define ENV "PERF_TEST_ATTR" | ||
31 | |||
32 | bool test_attr__enabled; | ||
33 | |||
34 | static char *dir; | ||
35 | |||
36 | void test_attr__init(void) | ||
37 | { | ||
38 | dir = getenv(ENV); | ||
39 | test_attr__enabled = (dir != NULL); | ||
40 | } | ||
41 | |||
42 | #define BUFSIZE 1024 | ||
43 | |||
44 | #define WRITE_ASS(str, fmt, data) \ | ||
45 | do { \ | ||
46 | char buf[BUFSIZE]; \ | ||
47 | size_t size; \ | ||
48 | \ | ||
49 | size = snprintf(buf, BUFSIZE, #str "=%"fmt "\n", data); \ | ||
50 | if (1 != fwrite(buf, size, 1, file)) { \ | ||
51 | perror("test attr - failed to write event file"); \ | ||
52 | fclose(file); \ | ||
53 | return -1; \ | ||
54 | } \ | ||
55 | \ | ||
56 | } while (0) | ||
57 | |||
58 | static int store_event(struct perf_event_attr *attr, pid_t pid, int cpu, | ||
59 | int fd, int group_fd, unsigned long flags) | ||
60 | { | ||
61 | FILE *file; | ||
62 | char path[PATH_MAX]; | ||
63 | |||
64 | snprintf(path, PATH_MAX, "%s/event-%d-%llu-%d", dir, | ||
65 | attr->type, attr->config, fd); | ||
66 | |||
67 | file = fopen(path, "w+"); | ||
68 | if (!file) { | ||
69 | perror("test attr - failed to open event file"); | ||
70 | return -1; | ||
71 | } | ||
72 | |||
73 | if (fprintf(file, "[event-%d-%llu-%d]\n", | ||
74 | attr->type, attr->config, fd) < 0) { | ||
75 | perror("test attr - failed to write event file"); | ||
76 | fclose(file); | ||
77 | return -1; | ||
78 | } | ||
79 | |||
80 | /* syscall arguments */ | ||
81 | WRITE_ASS(fd, "d", fd); | ||
82 | WRITE_ASS(group_fd, "d", group_fd); | ||
83 | WRITE_ASS(cpu, "d", cpu); | ||
84 | WRITE_ASS(pid, "d", pid); | ||
85 | WRITE_ASS(flags, "lu", flags); | ||
86 | |||
87 | /* struct perf_event_attr */ | ||
88 | WRITE_ASS(type, PRIu32, attr->type); | ||
89 | WRITE_ASS(size, PRIu32, attr->size); | ||
90 | WRITE_ASS(config, "llu", attr->config); | ||
91 | WRITE_ASS(sample_period, "llu", attr->sample_period); | ||
92 | WRITE_ASS(sample_type, "llu", attr->sample_type); | ||
93 | WRITE_ASS(read_format, "llu", attr->read_format); | ||
94 | WRITE_ASS(disabled, "d", attr->disabled); | ||
95 | WRITE_ASS(inherit, "d", attr->inherit); | ||
96 | WRITE_ASS(pinned, "d", attr->pinned); | ||
97 | WRITE_ASS(exclusive, "d", attr->exclusive); | ||
98 | WRITE_ASS(exclude_user, "d", attr->exclude_user); | ||
99 | WRITE_ASS(exclude_kernel, "d", attr->exclude_kernel); | ||
100 | WRITE_ASS(exclude_hv, "d", attr->exclude_hv); | ||
101 | WRITE_ASS(exclude_idle, "d", attr->exclude_idle); | ||
102 | WRITE_ASS(mmap, "d", attr->mmap); | ||
103 | WRITE_ASS(comm, "d", attr->comm); | ||
104 | WRITE_ASS(freq, "d", attr->freq); | ||
105 | WRITE_ASS(inherit_stat, "d", attr->inherit_stat); | ||
106 | WRITE_ASS(enable_on_exec, "d", attr->enable_on_exec); | ||
107 | WRITE_ASS(task, "d", attr->task); | ||
108 | WRITE_ASS(watermask, "d", attr->watermark); | ||
109 | WRITE_ASS(precise_ip, "d", attr->precise_ip); | ||
110 | WRITE_ASS(mmap_data, "d", attr->mmap_data); | ||
111 | WRITE_ASS(sample_id_all, "d", attr->sample_id_all); | ||
112 | WRITE_ASS(exclude_host, "d", attr->exclude_host); | ||
113 | WRITE_ASS(exclude_guest, "d", attr->exclude_guest); | ||
114 | WRITE_ASS(exclude_callchain_kernel, "d", | ||
115 | attr->exclude_callchain_kernel); | ||
116 | WRITE_ASS(exclude_callchain_user, "d", | ||
117 | attr->exclude_callchain_user); | ||
118 | WRITE_ASS(wakeup_events, PRIu32, attr->wakeup_events); | ||
119 | WRITE_ASS(bp_type, PRIu32, attr->bp_type); | ||
120 | WRITE_ASS(config1, "llu", attr->config1); | ||
121 | WRITE_ASS(config2, "llu", attr->config2); | ||
122 | WRITE_ASS(branch_sample_type, "llu", attr->branch_sample_type); | ||
123 | WRITE_ASS(sample_regs_user, "llu", attr->sample_regs_user); | ||
124 | WRITE_ASS(sample_stack_user, PRIu32, attr->sample_stack_user); | ||
125 | WRITE_ASS(optional, "d", 0); | ||
126 | |||
127 | fclose(file); | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, | ||
132 | int fd, int group_fd, unsigned long flags) | ||
133 | { | ||
134 | int errno_saved = errno; | ||
135 | |||
136 | if (store_event(attr, pid, cpu, fd, group_fd, flags)) | ||
137 | die("test attr FAILED"); | ||
138 | |||
139 | errno = errno_saved; | ||
140 | } | ||
diff --git a/tools/perf/tests/attr.py b/tools/perf/tests/attr.py new file mode 100644 index 000000000000..e98c726ac811 --- /dev/null +++ b/tools/perf/tests/attr.py | |||
@@ -0,0 +1,313 @@ | |||
1 | #! /usr/bin/python | ||
2 | |||
3 | import os | ||
4 | import sys | ||
5 | import glob | ||
6 | import optparse | ||
7 | import tempfile | ||
8 | import logging | ||
9 | import shutil | ||
10 | import ConfigParser | ||
11 | |||
12 | class Fail(Exception): | ||
13 | def __init__(self, test, msg): | ||
14 | self.msg = msg | ||
15 | self.test = test | ||
16 | def getMsg(self): | ||
17 | return '\'%s\' - %s' % (self.test.path, self.msg) | ||
18 | |||
19 | class Unsup(Exception): | ||
20 | def __init__(self, test): | ||
21 | self.test = test | ||
22 | def getMsg(self): | ||
23 | return '\'%s\'' % self.test.path | ||
24 | |||
25 | class Event(dict): | ||
26 | terms = [ | ||
27 | 'flags', | ||
28 | 'type', | ||
29 | 'size', | ||
30 | 'config', | ||
31 | 'sample_period', | ||
32 | 'sample_type', | ||
33 | 'read_format', | ||
34 | 'disabled', | ||
35 | 'inherit', | ||
36 | 'pinned', | ||
37 | 'exclusive', | ||
38 | 'exclude_user', | ||
39 | 'exclude_kernel', | ||
40 | 'exclude_hv', | ||
41 | 'exclude_idle', | ||
42 | 'mmap', | ||
43 | 'comm', | ||
44 | 'freq', | ||
45 | 'inherit_stat', | ||
46 | 'enable_on_exec', | ||
47 | 'task', | ||
48 | 'watermask', | ||
49 | 'precise_ip', | ||
50 | 'mmap_data', | ||
51 | 'sample_id_all', | ||
52 | 'exclude_host', | ||
53 | 'exclude_guest', | ||
54 | 'exclude_callchain_kernel', | ||
55 | 'exclude_callchain_user', | ||
56 | 'wakeup_events', | ||
57 | 'bp_type', | ||
58 | 'config1', | ||
59 | 'config2', | ||
60 | 'branch_sample_type', | ||
61 | 'sample_regs_user', | ||
62 | 'sample_stack_user', | ||
63 | ] | ||
64 | |||
65 | def add(self, data): | ||
66 | for key, val in data: | ||
67 | log.debug(" %s = %s" % (key, val)) | ||
68 | self[key] = val | ||
69 | |||
70 | def __init__(self, name, data, base): | ||
71 | log.info(" Event %s" % name); | ||
72 | self.name = name; | ||
73 | self.group = '' | ||
74 | self.add(base) | ||
75 | self.add(data) | ||
76 | |||
77 | def compare_data(self, a, b): | ||
78 | a_list = a.split('|') | ||
79 | b_list = b.split('|') | ||
80 | |||
81 | for a_item in a_list: | ||
82 | for b_item in b_list: | ||
83 | if (a_item == b_item): | ||
84 | return True | ||
85 | elif (a_item == '*') or (b_item == '*'): | ||
86 | return True | ||
87 | |||
88 | return False | ||
89 | |||
90 | def equal(self, other): | ||
91 | for t in Event.terms: | ||
92 | log.debug(" [%s] %s %s" % (t, self[t], other[t])); | ||
93 | if not self.has_key(t) or not other.has_key(t): | ||
94 | return False | ||
95 | if not self.compare_data(self[t], other[t]): | ||
96 | return False | ||
97 | return True | ||
98 | |||
99 | def is_optional(self): | ||
100 | if self['optional'] == '1': | ||
101 | return True | ||
102 | else: | ||
103 | return False | ||
104 | |||
105 | class Test(object): | ||
106 | def __init__(self, path, options): | ||
107 | parser = ConfigParser.SafeConfigParser() | ||
108 | parser.read(path) | ||
109 | |||
110 | log.warning("running '%s'" % path) | ||
111 | |||
112 | self.path = path | ||
113 | self.test_dir = options.test_dir | ||
114 | self.perf = options.perf | ||
115 | self.command = parser.get('config', 'command') | ||
116 | self.args = parser.get('config', 'args') | ||
117 | |||
118 | try: | ||
119 | self.ret = parser.get('config', 'ret') | ||
120 | except: | ||
121 | self.ret = 0 | ||
122 | |||
123 | self.expect = {} | ||
124 | self.result = {} | ||
125 | log.info(" loading expected events"); | ||
126 | self.load_events(path, self.expect) | ||
127 | |||
128 | def is_event(self, name): | ||
129 | if name.find("event") == -1: | ||
130 | return False | ||
131 | else: | ||
132 | return True | ||
133 | |||
134 | def load_events(self, path, events): | ||
135 | parser_event = ConfigParser.SafeConfigParser() | ||
136 | parser_event.read(path) | ||
137 | |||
138 | for section in filter(self.is_event, parser_event.sections()): | ||
139 | |||
140 | parser_items = parser_event.items(section); | ||
141 | base_items = {} | ||
142 | |||
143 | if (':' in section): | ||
144 | base = section[section.index(':') + 1:] | ||
145 | parser_base = ConfigParser.SafeConfigParser() | ||
146 | parser_base.read(self.test_dir + '/' + base) | ||
147 | base_items = parser_base.items('event') | ||
148 | |||
149 | e = Event(section, parser_items, base_items) | ||
150 | events[section] = e | ||
151 | |||
152 | def run_cmd(self, tempdir): | ||
153 | cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, | ||
154 | self.perf, self.command, tempdir, self.args) | ||
155 | ret = os.WEXITSTATUS(os.system(cmd)) | ||
156 | |||
157 | log.info(" running '%s' ret %d " % (cmd, ret)) | ||
158 | |||
159 | if ret != int(self.ret): | ||
160 | raise Unsup(self) | ||
161 | |||
162 | def compare(self, expect, result): | ||
163 | match = {} | ||
164 | |||
165 | log.info(" compare"); | ||
166 | |||
167 | # For each expected event find all matching | ||
168 | # events in result. Fail if there's not any. | ||
169 | for exp_name, exp_event in expect.items(): | ||
170 | exp_list = [] | ||
171 | log.debug(" matching [%s]" % exp_name) | ||
172 | for res_name, res_event in result.items(): | ||
173 | log.debug(" to [%s]" % res_name) | ||
174 | if (exp_event.equal(res_event)): | ||
175 | exp_list.append(res_name) | ||
176 | log.debug(" ->OK") | ||
177 | else: | ||
178 | log.debug(" ->FAIL"); | ||
179 | |||
180 | log.info(" match: [%s] optional(%d) matches %s" % | ||
181 | (exp_name, exp_event.is_optional(), str(exp_list))) | ||
182 | |||
183 | # we did not any matching event - fail | ||
184 | if (not exp_list) and (not exp_event.is_optional()): | ||
185 | raise Fail(self, 'match failure'); | ||
186 | |||
187 | match[exp_name] = exp_list | ||
188 | |||
189 | # For each defined group in the expected events | ||
190 | # check we match the same group in the result. | ||
191 | for exp_name, exp_event in expect.items(): | ||
192 | group = exp_event.group | ||
193 | |||
194 | if (group == ''): | ||
195 | continue | ||
196 | |||
197 | # XXX group matching does not account for | ||
198 | # optional events as above matching does | ||
199 | for res_name in match[exp_name]: | ||
200 | res_group = result[res_name].group | ||
201 | if res_group not in match[group]: | ||
202 | raise Fail(self, 'group failure') | ||
203 | |||
204 | log.info(" group: [%s] matches group leader %s" % | ||
205 | (exp_name, str(match[group]))) | ||
206 | |||
207 | log.info(" matched") | ||
208 | |||
209 | def resolve_groups(self, events): | ||
210 | for name, event in events.items(): | ||
211 | group_fd = event['group_fd']; | ||
212 | if group_fd == '-1': | ||
213 | continue; | ||
214 | |||
215 | for iname, ievent in events.items(): | ||
216 | if (ievent['fd'] == group_fd): | ||
217 | event.group = iname | ||
218 | log.debug('[%s] has group leader [%s]' % (name, iname)) | ||
219 | break; | ||
220 | |||
221 | def run(self): | ||
222 | tempdir = tempfile.mkdtemp(); | ||
223 | |||
224 | # run the test script | ||
225 | self.run_cmd(tempdir); | ||
226 | |||
227 | # load events expectation for the test | ||
228 | log.info(" loading result events"); | ||
229 | for f in glob.glob(tempdir + '/event*'): | ||
230 | self.load_events(f, self.result); | ||
231 | |||
232 | # resolve group_fd to event names | ||
233 | self.resolve_groups(self.expect); | ||
234 | self.resolve_groups(self.result); | ||
235 | |||
236 | # do the expectation - results matching - both ways | ||
237 | self.compare(self.expect, self.result) | ||
238 | self.compare(self.result, self.expect) | ||
239 | |||
240 | # cleanup | ||
241 | shutil.rmtree(tempdir) | ||
242 | |||
243 | |||
244 | def run_tests(options): | ||
245 | for f in glob.glob(options.test_dir + '/' + options.test): | ||
246 | try: | ||
247 | Test(f, options).run() | ||
248 | except Unsup, obj: | ||
249 | log.warning("unsupp %s" % obj.getMsg()) | ||
250 | |||
251 | def setup_log(verbose): | ||
252 | global log | ||
253 | level = logging.CRITICAL | ||
254 | |||
255 | if verbose == 1: | ||
256 | level = logging.WARNING | ||
257 | if verbose == 2: | ||
258 | level = logging.INFO | ||
259 | if verbose >= 3: | ||
260 | level = logging.DEBUG | ||
261 | |||
262 | log = logging.getLogger('test') | ||
263 | log.setLevel(level) | ||
264 | ch = logging.StreamHandler() | ||
265 | ch.setLevel(level) | ||
266 | formatter = logging.Formatter('%(message)s') | ||
267 | ch.setFormatter(formatter) | ||
268 | log.addHandler(ch) | ||
269 | |||
270 | USAGE = '''%s [OPTIONS] | ||
271 | -d dir # tests dir | ||
272 | -p path # perf binary | ||
273 | -t test # single test | ||
274 | -v # verbose level | ||
275 | ''' % sys.argv[0] | ||
276 | |||
277 | def main(): | ||
278 | parser = optparse.OptionParser(usage=USAGE) | ||
279 | |||
280 | parser.add_option("-t", "--test", | ||
281 | action="store", type="string", dest="test") | ||
282 | parser.add_option("-d", "--test-dir", | ||
283 | action="store", type="string", dest="test_dir") | ||
284 | parser.add_option("-p", "--perf", | ||
285 | action="store", type="string", dest="perf") | ||
286 | parser.add_option("-v", "--verbose", | ||
287 | action="count", dest="verbose") | ||
288 | |||
289 | options, args = parser.parse_args() | ||
290 | if args: | ||
291 | parser.error('FAILED wrong arguments %s' % ' '.join(args)) | ||
292 | return -1 | ||
293 | |||
294 | setup_log(options.verbose) | ||
295 | |||
296 | if not options.test_dir: | ||
297 | print 'FAILED no -d option specified' | ||
298 | sys.exit(-1) | ||
299 | |||
300 | if not options.test: | ||
301 | options.test = 'test*' | ||
302 | |||
303 | try: | ||
304 | run_tests(options) | ||
305 | |||
306 | except Fail, obj: | ||
307 | print "FAILED %s" % obj.getMsg(); | ||
308 | sys.exit(-1) | ||
309 | |||
310 | sys.exit(0) | ||
311 | |||
312 | if __name__ == '__main__': | ||
313 | main() | ||