Skip to content

Commit 4f46db3

Browse files
theihorKernel Patches Daemon
authored and
Kernel Patches Daemon
committed
scripts/bpf_doc.py: implement json output format
bpf_doc.py parses bpf.h header to collect information about various API elements (such as BPF helpers) and then dump them in one of the supported formats: rst docs and a C header. It's useful for external tools to be able to consume this information in an easy-to-parse format such as JSON. Implement JSON printers and add --json command line argument. v3->v4: refactor attrs to only be a helper's field v2->v3: nit cleanup v1->v2: add json printer for syscall target v3: https://lore.kernel.org/bpf/[email protected]/ v2: https://lore.kernel.org/bpf/[email protected]/ v1: https://lore.kernel.org/bpf/[email protected]/ Signed-off-by: Ihor Solodrai <[email protected]> Tested-by: Quentin Monnet <[email protected]> Reviewed-by: Quentin Monnet <[email protected]>
1 parent 4b2ad00 commit 4f46db3

File tree

1 file changed

+99
-20
lines changed

1 file changed

+99
-20
lines changed

scripts/bpf_doc.py

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from __future__ import print_function
99

1010
import argparse
11+
import json
1112
import re
1213
import sys, os
1314
import subprocess
@@ -37,11 +38,17 @@ class APIElement(object):
3738
@desc: textual description of the symbol
3839
@ret: (optional) description of any associated return value
3940
"""
40-
def __init__(self, proto='', desc='', ret='', attrs=[]):
41+
def __init__(self, proto='', desc='', ret=''):
4142
self.proto = proto
4243
self.desc = desc
4344
self.ret = ret
44-
self.attrs = attrs
45+
46+
def to_dict(self):
47+
return {
48+
'proto': self.proto,
49+
'desc': self.desc,
50+
'ret': self.ret
51+
}
4552

4653

4754
class Helper(APIElement):
@@ -51,8 +58,9 @@ class Helper(APIElement):
5158
@desc: textual description of the helper function
5259
@ret: description of the return value of the helper function
5360
"""
54-
def __init__(self, *args, **kwargs):
55-
super().__init__(*args, **kwargs)
61+
def __init__(self, proto='', desc='', ret='', attrs=[]):
62+
super().__init__(proto, desc, ret)
63+
self.attrs = attrs
5664
self.enum_val = None
5765

5866
def proto_break_down(self):
@@ -81,6 +89,12 @@ def proto_break_down(self):
8189

8290
return res
8391

92+
def to_dict(self):
93+
d = super().to_dict()
94+
d["attrs"] = self.attrs
95+
d.update(self.proto_break_down())
96+
return d
97+
8498

8599
ATTRS = {
86100
'__bpf_fastcall': 'bpf_fastcall'
@@ -675,7 +689,7 @@ def print_one(self, command):
675689
self.print_elem(command)
676690

677691

678-
class PrinterHelpers(Printer):
692+
class PrinterHelpersHeader(Printer):
679693
"""
680694
A printer for dumping collected information about helpers as C header to
681695
be included from BPF program.
@@ -896,6 +910,43 @@ def print_one(self, helper):
896910
print(') = (void *) %d;' % helper.enum_val)
897911
print('')
898912

913+
914+
class PrinterHelpersJSON(Printer):
915+
"""
916+
A printer for dumping collected information about helpers as a JSON file.
917+
@parser: A HeaderParser with Helper objects
918+
"""
919+
920+
def __init__(self, parser):
921+
self.elements = parser.helpers
922+
self.elem_number_check(
923+
parser.desc_unique_helpers,
924+
parser.define_unique_helpers,
925+
"helper",
926+
"___BPF_FUNC_MAPPER",
927+
)
928+
929+
def print_all(self):
930+
helper_dicts = [helper.to_dict() for helper in self.elements]
931+
out_dict = {'helpers': helper_dicts}
932+
print(json.dumps(out_dict, indent=4))
933+
934+
935+
class PrinterSyscallJSON(Printer):
936+
"""
937+
A printer for dumping collected syscall information as a JSON file.
938+
@parser: A HeaderParser with APIElement objects
939+
"""
940+
941+
def __init__(self, parser):
942+
self.elements = parser.commands
943+
self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
944+
945+
def print_all(self):
946+
syscall_dicts = [syscall.to_dict() for syscall in self.elements]
947+
out_dict = {'syscall': syscall_dicts}
948+
print(json.dumps(out_dict, indent=4))
949+
899950
###############################################################################
900951

901952
# If script is launched from scripts/ from kernel tree and can access
@@ -905,9 +956,17 @@ def print_one(self, helper):
905956
linuxRoot = os.path.dirname(os.path.dirname(script))
906957
bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h')
907958

959+
# target -> output format -> printer
908960
printers = {
909-
'helpers': PrinterHelpersRST,
910-
'syscall': PrinterSyscallRST,
961+
'helpers': {
962+
'rst': PrinterHelpersRST,
963+
'json': PrinterHelpersJSON,
964+
'header': PrinterHelpersHeader,
965+
},
966+
'syscall': {
967+
'rst': PrinterSyscallRST,
968+
'json': PrinterSyscallJSON
969+
},
911970
}
912971

913972
argParser = argparse.ArgumentParser(description="""
@@ -917,24 +976,44 @@ def print_one(self, helper):
917976
""")
918977
argParser.add_argument('--header', action='store_true',
919978
help='generate C header file')
979+
argParser.add_argument('--json', action='store_true',
980+
help='generate a JSON')
920981
if (os.path.isfile(bpfh)):
921982
argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h',
922983
default=bpfh)
923984
else:
924985
argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h')
925986
argParser.add_argument('target', nargs='?', default='helpers',
926987
choices=printers.keys(), help='eBPF API target')
927-
args = argParser.parse_args()
928-
929-
# Parse file.
930-
headerParser = HeaderParser(args.filename)
931-
headerParser.run()
932988

933-
# Print formatted output to standard output.
934-
if args.header:
935-
if args.target != 'helpers':
936-
raise NotImplementedError('Only helpers header generation is supported')
937-
printer = PrinterHelpers(headerParser)
938-
else:
939-
printer = printers[args.target](headerParser)
940-
printer.print_all()
989+
def error_die(message: str):
990+
argParser.print_usage(file=sys.stderr)
991+
print('Error: {}'.format(message), file=sys.stderr)
992+
exit(1)
993+
994+
def parse_and_dump():
995+
args = argParser.parse_args()
996+
997+
# Parse file.
998+
headerParser = HeaderParser(args.filename)
999+
headerParser.run()
1000+
1001+
if args.header and args.json:
1002+
error_die('Use either --header or --json, not both')
1003+
1004+
output_format = 'rst'
1005+
if args.header:
1006+
output_format = 'header'
1007+
elif args.json:
1008+
output_format = 'json'
1009+
1010+
try:
1011+
printer = printers[args.target][output_format](headerParser)
1012+
# Print formatted output to standard output.
1013+
printer.print_all()
1014+
except KeyError:
1015+
error_die('Unsupported target/format combination: "{}", "{}"'
1016+
.format(args.target, output_format))
1017+
1018+
if __name__ == "__main__":
1019+
parse_and_dump()

0 commit comments

Comments
 (0)