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/20250507203034.270428-1-isolodrai@meta.com/
v2: https://lore.kernel.org/bpf/20250507182802.3833349-1-isolodrai@meta.com/
v1: https://lore.kernel.org/bpf/20250506000605.497296-1-isolodrai@meta.com/

Signed-off-by: Ihor Solodrai <isolodrai@meta.com>
Tested-by: Quentin Monnet <qmo@kernel.org>
Reviewed-by: Quentin Monnet <qmo@kernel.org>
Link: https://lore.kernel.org/r/20250508203708.2520847-1-isolodrai@meta.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Ihor Solodrai 2025-05-08 13:37:08 -07:00 committed by Alexei Starovoitov
parent cf15cdc0f0
commit cb4a119252

View file

@ -8,6 +8,7 @@
from __future__ import print_function
import argparse
import json
import re
import sys, os
import subprocess
@ -37,11 +38,17 @@ class APIElement(object):
@desc: textual description of the symbol
@ret: (optional) description of any associated return value
"""
def __init__(self, proto='', desc='', ret='', attrs=[]):
def __init__(self, proto='', desc='', ret=''):
self.proto = proto
self.desc = desc
self.ret = ret
self.attrs = attrs
def to_dict(self):
return {
'proto': self.proto,
'desc': self.desc,
'ret': self.ret
}
class Helper(APIElement):
@ -51,8 +58,9 @@ class Helper(APIElement):
@desc: textual description of the helper function
@ret: description of the return value of the helper function
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, proto='', desc='', ret='', attrs=[]):
super().__init__(proto, desc, ret)
self.attrs = attrs
self.enum_val = None
def proto_break_down(self):
@ -81,6 +89,12 @@ class Helper(APIElement):
return res
def to_dict(self):
d = super().to_dict()
d["attrs"] = self.attrs
d.update(self.proto_break_down())
return d
ATTRS = {
'__bpf_fastcall': 'bpf_fastcall'
@ -675,7 +689,7 @@ COMMANDS
self.print_elem(command)
class PrinterHelpers(Printer):
class PrinterHelpersHeader(Printer):
"""
A printer for dumping collected information about helpers as C header to
be included from BPF program.
@ -896,6 +910,43 @@ class PrinterHelpers(Printer):
print(') = (void *) %d;' % helper.enum_val)
print('')
class PrinterHelpersJSON(Printer):
"""
A printer for dumping collected information about helpers as a JSON file.
@parser: A HeaderParser with Helper objects
"""
def __init__(self, parser):
self.elements = parser.helpers
self.elem_number_check(
parser.desc_unique_helpers,
parser.define_unique_helpers,
"helper",
"___BPF_FUNC_MAPPER",
)
def print_all(self):
helper_dicts = [helper.to_dict() for helper in self.elements]
out_dict = {'helpers': helper_dicts}
print(json.dumps(out_dict, indent=4))
class PrinterSyscallJSON(Printer):
"""
A printer for dumping collected syscall information as a JSON file.
@parser: A HeaderParser with APIElement objects
"""
def __init__(self, parser):
self.elements = parser.commands
self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
def print_all(self):
syscall_dicts = [syscall.to_dict() for syscall in self.elements]
out_dict = {'syscall': syscall_dicts}
print(json.dumps(out_dict, indent=4))
###############################################################################
# If script is launched from scripts/ from kernel tree and can access
@ -905,9 +956,17 @@ script = os.path.abspath(sys.argv[0])
linuxRoot = os.path.dirname(os.path.dirname(script))
bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h')
# target -> output format -> printer
printers = {
'helpers': PrinterHelpersRST,
'syscall': PrinterSyscallRST,
'helpers': {
'rst': PrinterHelpersRST,
'json': PrinterHelpersJSON,
'header': PrinterHelpersHeader,
},
'syscall': {
'rst': PrinterSyscallRST,
'json': PrinterSyscallJSON
},
}
argParser = argparse.ArgumentParser(description="""
@ -917,6 +976,8 @@ rst2man utility.
""")
argParser.add_argument('--header', action='store_true',
help='generate C header file')
argParser.add_argument('--json', action='store_true',
help='generate a JSON')
if (os.path.isfile(bpfh)):
argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h',
default=bpfh)
@ -924,17 +985,35 @@ else:
argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h')
argParser.add_argument('target', nargs='?', default='helpers',
choices=printers.keys(), help='eBPF API target')
args = argParser.parse_args()
# Parse file.
headerParser = HeaderParser(args.filename)
headerParser.run()
def error_die(message: str):
argParser.print_usage(file=sys.stderr)
print('Error: {}'.format(message), file=sys.stderr)
exit(1)
# Print formatted output to standard output.
if args.header:
if args.target != 'helpers':
raise NotImplementedError('Only helpers header generation is supported')
printer = PrinterHelpers(headerParser)
else:
printer = printers[args.target](headerParser)
printer.print_all()
def parse_and_dump():
args = argParser.parse_args()
# Parse file.
headerParser = HeaderParser(args.filename)
headerParser.run()
if args.header and args.json:
error_die('Use either --header or --json, not both')
output_format = 'rst'
if args.header:
output_format = 'header'
elif args.json:
output_format = 'json'
try:
printer = printers[args.target][output_format](headerParser)
# Print formatted output to standard output.
printer.print_all()
except KeyError:
error_die('Unsupported target/format combination: "{}", "{}"'
.format(args.target, output_format))
if __name__ == "__main__":
parse_and_dump()