Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Ihor Solodrai and committed by
Alexei Starovoitov
cb4a1192 cf15cdc0

+98 -19
+98 -19
scripts/bpf_doc.py
··· 8 8 from __future__ import print_function 9 9 10 10 import argparse 11 + import json 11 12 import re 12 13 import sys, os 13 14 import subprocess ··· 38 37 @desc: textual description of the symbol 39 38 @ret: (optional) description of any associated return value 40 39 """ 41 - def __init__(self, proto='', desc='', ret='', attrs=[]): 40 + def __init__(self, proto='', desc='', ret=''): 42 41 self.proto = proto 43 42 self.desc = desc 44 43 self.ret = ret 45 - self.attrs = attrs 44 + 45 + def to_dict(self): 46 + return { 47 + 'proto': self.proto, 48 + 'desc': self.desc, 49 + 'ret': self.ret 50 + } 46 51 47 52 48 53 class Helper(APIElement): ··· 58 51 @desc: textual description of the helper function 59 52 @ret: description of the return value of the helper function 60 53 """ 61 - def __init__(self, *args, **kwargs): 62 - super().__init__(*args, **kwargs) 54 + def __init__(self, proto='', desc='', ret='', attrs=[]): 55 + super().__init__(proto, desc, ret) 56 + self.attrs = attrs 63 57 self.enum_val = None 64 58 65 59 def proto_break_down(self): ··· 88 80 }) 89 81 90 82 return res 83 + 84 + def to_dict(self): 85 + d = super().to_dict() 86 + d["attrs"] = self.attrs 87 + d.update(self.proto_break_down()) 88 + return d 91 89 92 90 93 91 ATTRS = { ··· 689 675 self.print_elem(command) 690 676 691 677 692 - class PrinterHelpers(Printer): 678 + class PrinterHelpersHeader(Printer): 693 679 """ 694 680 A printer for dumping collected information about helpers as C header to 695 681 be included from BPF program. ··· 910 896 print(') = (void *) %d;' % helper.enum_val) 911 897 print('') 912 898 899 + 900 + class PrinterHelpersJSON(Printer): 901 + """ 902 + A printer for dumping collected information about helpers as a JSON file. 903 + @parser: A HeaderParser with Helper objects 904 + """ 905 + 906 + def __init__(self, parser): 907 + self.elements = parser.helpers 908 + self.elem_number_check( 909 + parser.desc_unique_helpers, 910 + parser.define_unique_helpers, 911 + "helper", 912 + "___BPF_FUNC_MAPPER", 913 + ) 914 + 915 + def print_all(self): 916 + helper_dicts = [helper.to_dict() for helper in self.elements] 917 + out_dict = {'helpers': helper_dicts} 918 + print(json.dumps(out_dict, indent=4)) 919 + 920 + 921 + class PrinterSyscallJSON(Printer): 922 + """ 923 + A printer for dumping collected syscall information as a JSON file. 924 + @parser: A HeaderParser with APIElement objects 925 + """ 926 + 927 + def __init__(self, parser): 928 + self.elements = parser.commands 929 + self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd') 930 + 931 + def print_all(self): 932 + syscall_dicts = [syscall.to_dict() for syscall in self.elements] 933 + out_dict = {'syscall': syscall_dicts} 934 + print(json.dumps(out_dict, indent=4)) 935 + 913 936 ############################################################################### 914 937 915 938 # If script is launched from scripts/ from kernel tree and can access ··· 956 905 linuxRoot = os.path.dirname(os.path.dirname(script)) 957 906 bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h') 958 907 908 + # target -> output format -> printer 959 909 printers = { 960 - 'helpers': PrinterHelpersRST, 961 - 'syscall': PrinterSyscallRST, 910 + 'helpers': { 911 + 'rst': PrinterHelpersRST, 912 + 'json': PrinterHelpersJSON, 913 + 'header': PrinterHelpersHeader, 914 + }, 915 + 'syscall': { 916 + 'rst': PrinterSyscallRST, 917 + 'json': PrinterSyscallJSON 918 + }, 962 919 } 963 920 964 921 argParser = argparse.ArgumentParser(description=""" ··· 976 917 """) 977 918 argParser.add_argument('--header', action='store_true', 978 919 help='generate C header file') 920 + argParser.add_argument('--json', action='store_true', 921 + help='generate a JSON') 979 922 if (os.path.isfile(bpfh)): 980 923 argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h', 981 924 default=bpfh) ··· 985 924 argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h') 986 925 argParser.add_argument('target', nargs='?', default='helpers', 987 926 choices=printers.keys(), help='eBPF API target') 988 - args = argParser.parse_args() 989 927 990 - # Parse file. 991 - headerParser = HeaderParser(args.filename) 992 - headerParser.run() 928 + def error_die(message: str): 929 + argParser.print_usage(file=sys.stderr) 930 + print('Error: {}'.format(message), file=sys.stderr) 931 + exit(1) 993 932 994 - # Print formatted output to standard output. 995 - if args.header: 996 - if args.target != 'helpers': 997 - raise NotImplementedError('Only helpers header generation is supported') 998 - printer = PrinterHelpers(headerParser) 999 - else: 1000 - printer = printers[args.target](headerParser) 1001 - printer.print_all() 933 + def parse_and_dump(): 934 + args = argParser.parse_args() 935 + 936 + # Parse file. 937 + headerParser = HeaderParser(args.filename) 938 + headerParser.run() 939 + 940 + if args.header and args.json: 941 + error_die('Use either --header or --json, not both') 942 + 943 + output_format = 'rst' 944 + if args.header: 945 + output_format = 'header' 946 + elif args.json: 947 + output_format = 'json' 948 + 949 + try: 950 + printer = printers[args.target][output_format](headerParser) 951 + # Print formatted output to standard output. 952 + printer.print_all() 953 + except KeyError: 954 + error_die('Unsupported target/format combination: "{}", "{}"' 955 + .format(args.target, output_format)) 956 + 957 + if __name__ == "__main__": 958 + parse_and_dump()