diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/nvgpu_debug_hal.py | 123 | ||||
-rwxr-xr-x | scripts/rfr | 233 |
2 files changed, 356 insertions, 0 deletions
diff --git a/scripts/nvgpu_debug_hal.py b/scripts/nvgpu_debug_hal.py new file mode 100755 index 00000000..a56d66e3 --- /dev/null +++ b/scripts/nvgpu_debug_hal.py | |||
@@ -0,0 +1,123 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | # | ||
3 | # Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. | ||
4 | # | ||
5 | # This program is free software; you can redistribute it and/or modify it | ||
6 | # under the terms and conditions of the GNU General Public License, | ||
7 | # version 2, as published by the Free Software Foundation. | ||
8 | # | ||
9 | # This program is distributed in the hope it will be useful, but WITHOUT | ||
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | # more details. | ||
13 | # | ||
14 | # usage: nvgpu_debug_hal.py [-h] [--csv] [--gk20a GK20A] [gops_filename] | ||
15 | # | ||
16 | # Analyze the HAL debugfs interface's output. With no arguments, prints out | ||
17 | # statistics on the gpu_ops struct based on analysis of gk20a.h | ||
18 | # | ||
19 | # positional arguments: | ||
20 | # gops_filename debugfs interface output file (from /d/gpu.0/hal/gops) | ||
21 | # | ||
22 | # optional arguments: | ||
23 | # -h, --help show this help message and exit | ||
24 | # --csv csv formatted output | ||
25 | # --gk20a GK20A path to gk20a.h | ||
26 | |||
27 | import argparse | ||
28 | import re | ||
29 | from os import environ | ||
30 | |||
31 | description_str = ('Analyze the HAL debugfs interface\'s output. ' | ||
32 | 'With no arguments, prints out statistics on the gpu_ops struct based on ' | ||
33 | 'analysis of gk20a.h') | ||
34 | |||
35 | parser = argparse.ArgumentParser(description=description_str); | ||
36 | parser.add_argument("--csv", help="csv formatted output", action="store_true"); | ||
37 | parser.add_argument("--gk20a", help="path to gk20a.h"); | ||
38 | parser.add_argument("gops_filename", help="debugfs interface output file (from /d/gpu.0/hal/gops)", nargs='?'); | ||
39 | args = parser.parse_args(); | ||
40 | |||
41 | if args.gk20a: | ||
42 | gk20a_h_path = args.gk20a | ||
43 | else: | ||
44 | top = environ.get('TOP'); | ||
45 | if top is None: | ||
46 | print("$TOP is undefined, unable to find gk20a.h"); | ||
47 | exit(-1); | ||
48 | gk20a_h_path = top + "/kernel/nvgpu/drivers/gpu/nvgpu/gk20a/gk20a.h" | ||
49 | |||
50 | def get_function_pointer_name(line): | ||
51 | matches = re.search('.*\(\*(?P<function_name>\w+)\)\(', line); | ||
52 | if matches is None: | ||
53 | return None | ||
54 | else: | ||
55 | return matches.group("function_name"); | ||
56 | |||
57 | # Build the list of gpu_ops member function pointers from gk20a.h | ||
58 | non_function_pointer_members = []; | ||
59 | formatted_members = []; | ||
60 | gops_members = dict(); | ||
61 | substruct_names = []; | ||
62 | lone_members = []; | ||
63 | with open(gk20a_h_path) as gk20a_h: | ||
64 | # Skip to start of gpu_ops struct | ||
65 | while gk20a_h.readline() != "struct gpu_ops {\n": | ||
66 | continue; | ||
67 | |||
68 | line = gk20a_h.readline(); | ||
69 | while line != "};\n": | ||
70 | # If this is a substruct | ||
71 | if re.match('\t+struct.+\{', line): | ||
72 | # Read the contents of the substruct | ||
73 | line = gk20a_h.readline(); | ||
74 | struct_contents = "" | ||
75 | while not re.match("\t*\} (\w+);", line): | ||
76 | struct_contents += line; | ||
77 | line = gk20a_h.readline(); | ||
78 | # Split out the substruct name and the function pointer names | ||
79 | struct_name = re.match("\t*\} (?P<struct_name>\w+);", line).group("struct_name"); | ||
80 | struct_members = re.findall(r".+?\(\s*\*\s*(\w+)\s*\).+?;", struct_contents, flags=re.DOTALL) | ||
81 | |||
82 | # Store the substruct as an entry | ||
83 | substruct_names.append(struct_name); | ||
84 | gops_members[struct_name] = struct_members; | ||
85 | # Format members | ||
86 | for member in struct_members: | ||
87 | formatted_members.append(struct_name + "." + member); | ||
88 | else: | ||
89 | # Lone members (function pointers or stuff not in a substruct) | ||
90 | match = re.match(".*\(\*(?P<function_name>\w+)\)\(", line); | ||
91 | if match is not None: | ||
92 | # It's a function pointer, keep track of it | ||
93 | lone_members.append(match.group("function_name")); | ||
94 | formatted_members.append(match.group("function_name")); | ||
95 | else: | ||
96 | # Not a function pointer, may also catch comments etc. | ||
97 | non_function_pointer_members.append(line.strip()); | ||
98 | line = gk20a_h.readline(); | ||
99 | if args.gops_filename: | ||
100 | # Interpret gops file | ||
101 | with open(args.gops_filename) as gops: | ||
102 | i = 0; | ||
103 | # Option for csv output | ||
104 | if args.csv: | ||
105 | format_string = '{0},{1}'; | ||
106 | else: | ||
107 | format_string = '{0:<60} = {1}'; | ||
108 | for line in gops: | ||
109 | print(format_string.format(formatted_members[i], line[:-1])); | ||
110 | i += 1; | ||
111 | else: | ||
112 | # Just print some stats on the gpu_ops struct | ||
113 | total = 0; | ||
114 | print("----- Lone Function Pointers -----"); | ||
115 | print("Count =", len(lone_members)); | ||
116 | total += len(lone_members); | ||
117 | for line in lone_members: | ||
118 | print(line); | ||
119 | print("----- Substruct Counts -----"); | ||
120 | for name in substruct_names: | ||
121 | print(name, "=", len(gops_members[name])); | ||
122 | total += len(gops_members[name]) | ||
123 | print("\n Total =", total); | ||
diff --git a/scripts/rfr b/scripts/rfr new file mode 100755 index 00000000..bca32d3b --- /dev/null +++ b/scripts/rfr | |||
@@ -0,0 +1,233 @@ | |||
1 | #!/usr/bin/python | ||
2 | # | ||
3 | # Simple script for formatting RFR messages for nvgpu changes. | ||
4 | # | ||
5 | |||
6 | import os | ||
7 | import re | ||
8 | import json | ||
9 | import argparse | ||
10 | import subprocess | ||
11 | |||
12 | VERSION = '1.0.0' | ||
13 | |||
14 | # Gerrit commit URL formats. These are regular expressions to match the | ||
15 | # incoming URLs against. | ||
16 | gr_fmts = [ r'http://git-master/r/(\d+)', | ||
17 | r'http://git-master/r/#/c/(\d+)/', | ||
18 | r'http://git-master.nvidia.com/r/(\d+)', | ||
19 | r'http://git-master.nvidia.com/r/#/c/(\d+)/', | ||
20 | r'https://git-master/r/(\d+)', | ||
21 | r'https://git-master/r/#/c/(\d+)/', | ||
22 | r'https://git-master.nvidia.com/r/(\d+)', | ||
23 | r'https://git-master.nvidia.com/r/#/c/(\d+)/' ] | ||
24 | |||
25 | # The user to use. May be overridden but the default comes from the environment. | ||
26 | user = os.environ['USER'] | ||
27 | |||
28 | # Gerrit query command to obtain the patch URL. The substitution will be the | ||
29 | # gerrit Change-ID parsed from the git commit message. | ||
30 | gr_query_cmd = 'ssh %s@git-master -p 29418 gerrit query --format json %s' | ||
31 | |||
32 | def parse_args(): | ||
33 | """ | ||
34 | Parse arguments to rfr. | ||
35 | """ | ||
36 | |||
37 | ep="""This program will format commit messages into something that can be | ||
38 | sent to the nvgpu mailing list for review | ||
39 | """ | ||
40 | help_msg="""Git or gerrit commits to describe. Can be either a git or gerrit | ||
41 | commit ID. If the ID starts with a 'I' then it will be treated as a gerrit ID. | ||
42 | If the commit ID looks like a gerrit URL then it is treated as a gerrit URL. | ||
43 | Otherwise it's treated as a git commit ID. | ||
44 | """ | ||
45 | parser = argparse.ArgumentParser(description='RFR formatting tool', | ||
46 | epilog=ep) | ||
47 | |||
48 | parser.add_argument('-V', '--version', action='store_true', default=False, | ||
49 | help='print the program version') | ||
50 | parser.add_argument('-m', '--msg', action='store', default=None, | ||
51 | help='Custom message to add to the RFR email') | ||
52 | |||
53 | # Positionals: the gerrit URLs. | ||
54 | parser.add_argument('commits', metavar='Commit-IDs', | ||
55 | nargs='+', | ||
56 | help=help_msg) | ||
57 | |||
58 | arg_parser = parser.parse_args() | ||
59 | |||
60 | return arg_parser | ||
61 | |||
62 | def get_gerrit_url_id(cmt): | ||
63 | """ | ||
64 | Determines if the passed cmt is a gerrit commit URL. If it is then this | ||
65 | returns the URL ID; otherwise it returns None. | ||
66 | """ | ||
67 | |||
68 | for fmt in gr_fmts: | ||
69 | p = re.compile(fmt) | ||
70 | m = p.search(cmt) | ||
71 | if m: | ||
72 | return m.group(1) | ||
73 | |||
74 | return None | ||
75 | |||
76 | def gerrit_query(change): | ||
77 | """ | ||
78 | Query gerrit for the JSON change information. Return a python object | ||
79 | describing the JSON data. | ||
80 | |||
81 | change can either be a Change-Id or the numeric change number from a URL. | ||
82 | |||
83 | Note there is an interesting limitation with this: gerrit can have multiple | ||
84 | changes with the same Change-Id (./sigh). So if you query a change ID that | ||
85 | points to multiple changes you get back all of them. | ||
86 | |||
87 | This script just uses the first. Ideally one could filter by branch or by | ||
88 | some other distinguishing factor. | ||
89 | """ | ||
90 | |||
91 | query_cmd = gr_query_cmd % (user, change) | ||
92 | |||
93 | prog = subprocess.Popen(query_cmd, shell=True, | ||
94 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
95 | |||
96 | stdout_data, stderr_data = prog.communicate() | ||
97 | if prog.returncode != 0: | ||
98 | print('`%s\' failed!' % query_cmd) | ||
99 | return False | ||
100 | |||
101 | commit = json.loads(stdout_data.decode('utf-8').splitlines()[0]) | ||
102 | if 'id' not in commit: | ||
103 | print('%s is not a gerrit commit!?' % change) | ||
104 | print('Most likely you need to push the change.') | ||
105 | return None | ||
106 | |||
107 | return commit | ||
108 | |||
109 | def commit_info_from_gerrit_change_id(change_id): | ||
110 | """ | ||
111 | Return a dict with all the gerrit info from a gerrit change ID. | ||
112 | """ | ||
113 | |||
114 | return gerrit_query(change_id) | ||
115 | |||
116 | def commit_info_from_git_sha1(rev): | ||
117 | """ | ||
118 | Return a dict with all the gerrit info from a git sha1 rev. | ||
119 | """ | ||
120 | |||
121 | return gerrit_query(rev) | ||
122 | |||
123 | def commit_info_from_gerrit_cl(cmt): | ||
124 | """ | ||
125 | Return a dict with all the gerrit info from a Gerrit URL. | ||
126 | """ | ||
127 | |||
128 | cl = get_gerrit_url_id(cmt) | ||
129 | if not cl: | ||
130 | return None | ||
131 | |||
132 | return gerrit_query(cl) | ||
133 | |||
134 | def git_sha1_from_commit(commit_ish): | ||
135 | """ | ||
136 | Return sha1 revision from a commit-ish string, or None if this doesn't | ||
137 | appear to be a commit. | ||
138 | """ | ||
139 | |||
140 | prog = subprocess.Popen('git rev-parse %s' % commit_ish, shell=True, | ||
141 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
142 | |||
143 | stdout_data, stderr_data = prog.communicate() | ||
144 | if prog.returncode != 0: | ||
145 | print('`git rev-parse %s\' failed?!' % commit_ish) | ||
146 | return None | ||
147 | |||
148 | return stdout_data.decode('utf-8') | ||
149 | |||
150 | def indent_lines(text, ind): | ||
151 | """ | ||
152 | Prepend each new line in the passed text with ind. | ||
153 | """ | ||
154 | return ''.join(ind + l + '\n' for l in text.splitlines()) | ||
155 | |||
156 | def display_commits(commits_info, extra_message): | ||
157 | """ | ||
158 | Takes a list of the commit info objects to print. | ||
159 | """ | ||
160 | |||
161 | whole_template = """ | ||
162 | Hi All, | ||
163 | |||
164 | I would like you to review the following changes. | ||
165 | {extra_message} | ||
166 | {cmt_descriptions} | ||
167 | Thanks! | ||
168 | {cmt_verbose}""" | ||
169 | |||
170 | cmt_template = """ | ||
171 | +---------------------------------------- | ||
172 | | {url} | ||
173 | | Author: {author} | ||
174 | |||
175 | {cmtmsg}""" | ||
176 | |||
177 | cmt_descriptions = '' | ||
178 | for c in commits_info: | ||
179 | cmt_descriptions += " %s - %s\n" % (c['url'], c['subject']) | ||
180 | |||
181 | # Add new lines around the extra_message, if applicable. Otherwise we don't | ||
182 | # want anything to show up for extra_message. | ||
183 | if extra_message: | ||
184 | extra_message = '\n%s\n' % extra_message | ||
185 | else: | ||
186 | extra_message = '' | ||
187 | |||
188 | cmt_verbose = '' | ||
189 | for c in commits_info: | ||
190 | cmt_verbose += cmt_template.format(url=c['url'], | ||
191 | author=c['owner']['name'], | ||
192 | cmtmsg=indent_lines( | ||
193 | c['commitMessage'], ' ')) | ||
194 | |||
195 | print(whole_template.format(cmt_descriptions=cmt_descriptions, | ||
196 | extra_message=extra_message, | ||
197 | cmt_verbose=cmt_verbose)) | ||
198 | |||
199 | def main(): | ||
200 | """ | ||
201 | The magic happens here. | ||
202 | """ | ||
203 | |||
204 | arg_parser = parse_args() | ||
205 | commits_info = [ ] | ||
206 | |||
207 | if arg_parser.version: | ||
208 | print('Version: %s' % VERSION) | ||
209 | exit(0) | ||
210 | |||
211 | # Builds a dictionary of Gerrit Change-Ids. From the Change-Ids we can then | ||
212 | # get the commit message and URL. | ||
213 | # | ||
214 | # This also builds an array of those same commit IDs to track the ordering | ||
215 | # of the commits so that the user can choose the order of the patches based | ||
216 | # on the order in which they pass the commits. | ||
217 | for cmt in arg_parser.commits: | ||
218 | if cmt[0] == 'I': | ||
219 | info = commit_info_from_gerrit_change_id(cmt) | ||
220 | elif get_gerrit_url_id(cmt): | ||
221 | info = commit_info_from_gerrit_cl(cmt) | ||
222 | else: | ||
223 | info = commit_info_from_git_sha1(git_sha1_from_commit(cmt)) | ||
224 | |||
225 | if info: | ||
226 | commits_info.append(info) | ||
227 | else: | ||
228 | print('Warning: \'%s\' doesn\'t appear to be a commit!' % cmt) | ||
229 | |||
230 | display_commits(commits_info, arg_parser.msg) | ||
231 | |||
232 | if __name__ == '__main__': | ||
233 | main() | ||