xref: /aosp_15_r20/external/crosvm/tools/bench (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2023 The ChromiumOS Authors
3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
5*bb4ee6a4SAndroid Build Coastguard Worker
6*bb4ee6a4SAndroid Build Coastguard Workerimport json
7*bb4ee6a4SAndroid Build Coastguard Workerimport os
8*bb4ee6a4SAndroid Build Coastguard Workerimport pathlib
9*bb4ee6a4SAndroid Build Coastguard Workerimport re
10*bb4ee6a4SAndroid Build Coastguard Workerimport tempfile
11*bb4ee6a4SAndroid Build Coastguard Workerfrom collections import Counter
12*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Dict, List, Tuple
13*bb4ee6a4SAndroid Build Coastguard Worker
14*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import CROSVM_ROOT, Triple, chdir, cmd, run_main
15*bb4ee6a4SAndroid Build Coastguard Worker
16*bb4ee6a4SAndroid Build Coastguard Worker# Capture syscall name as group 1 from strace log
17*bb4ee6a4SAndroid Build Coastguard Worker# E.g. 'access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)' will have 'access' as group 1
18*bb4ee6a4SAndroid Build Coastguard Workersyscall_re = re.compile(r"^(\w+)\(.+?= .+$", re.IGNORECASE | re.MULTILINE)
19*bb4ee6a4SAndroid Build Coastguard Worker
20*bb4ee6a4SAndroid Build Coastguard Worker# Capture seccomp_trace json as group 1 from crosvm log
21*bb4ee6a4SAndroid Build Coastguard Worker# E.g. ' DEBUG jail::helpers] seccomp_trace {"event": "minijail_create", "name": "balloon_device", "jail_addr": "0x55623bea1df0"}'
22*bb4ee6a4SAndroid Build Coastguard Worker# will have the entire json string as group 1
23*bb4ee6a4SAndroid Build Coastguard Workerseccomp_trace_log_re = re.compile(r"DEBUG.*? seccomp_trace .*?(\{.*\}).*?$", re.MULTILINE)
24*bb4ee6a4SAndroid Build Coastguard Worker
25*bb4ee6a4SAndroid Build Coastguard Worker
26*bb4ee6a4SAndroid Build Coastguard Workerdef parse_strace_file_str_to_freq_dict(strace_content: str) -> Counter[str]:
27*bb4ee6a4SAndroid Build Coastguard Worker    # TODO: start from after seccomp
28*bb4ee6a4SAndroid Build Coastguard Worker    # TODO: throw exception when seccomp isn't detected
29*bb4ee6a4SAndroid Build Coastguard Worker    return Counter(map(lambda m: m.group(1), syscall_re.finditer(strace_content)))
30*bb4ee6a4SAndroid Build Coastguard Worker
31*bb4ee6a4SAndroid Build Coastguard Worker
32*bb4ee6a4SAndroid Build Coastguard Workerdef freq_dict_to_freq_file_str(freq_dict: Dict[str, int]) -> str:
33*bb4ee6a4SAndroid Build Coastguard Worker    return "\n".join(map(lambda k: f"{k}: {str(freq_dict[k])}", sorted(freq_dict.keys())))
34*bb4ee6a4SAndroid Build Coastguard Worker
35*bb4ee6a4SAndroid Build Coastguard Worker
36*bb4ee6a4SAndroid Build Coastguard Workerdef parse_crosvm_log(crosvm_log: str) -> List[Dict[str, str]]:
37*bb4ee6a4SAndroid Build Coastguard Worker    # Load each seccomp_trace event json as dict
38*bb4ee6a4SAndroid Build Coastguard Worker    seccomp_trace_events = []
39*bb4ee6a4SAndroid Build Coastguard Worker    for match in seccomp_trace_log_re.finditer(crosvm_log):
40*bb4ee6a4SAndroid Build Coastguard Worker        seccomp_trace_events.append(json.loads(match.group(1)))
41*bb4ee6a4SAndroid Build Coastguard Worker    return seccomp_trace_events
42*bb4ee6a4SAndroid Build Coastguard Worker
43*bb4ee6a4SAndroid Build Coastguard Worker
44*bb4ee6a4SAndroid Build Coastguard Workerdef parse_seccomp_trace_events_to_pid_table(
45*bb4ee6a4SAndroid Build Coastguard Worker    seccomp_trace_events: List[Dict[str, str]]
46*bb4ee6a4SAndroid Build Coastguard Worker) -> List[Tuple[int, str]]:
47*bb4ee6a4SAndroid Build Coastguard Worker    # There are 3 types of minijail events: create, clone, form
48*bb4ee6a4SAndroid Build Coastguard Worker    # Create is when a jail is created and a policy file is loaded into the new jail. "name" field in this type of event will contain the policy file name used in this jail.
49*bb4ee6a4SAndroid Build Coastguard Worker    # Clone is when a jail's policy is duplicated into a new jail. Cloned jail can be used to contain different processes with the same policy.
50*bb4ee6a4SAndroid Build Coastguard Worker    # Fork is when a jail is enabled and a process is forked to be executed inside the jail.
51*bb4ee6a4SAndroid Build Coastguard Worker    addr_to_name: Dict[str, str] = dict()
52*bb4ee6a4SAndroid Build Coastguard Worker    result = []
53*bb4ee6a4SAndroid Build Coastguard Worker    for event in seccomp_trace_events:
54*bb4ee6a4SAndroid Build Coastguard Worker        if event["event"] == "minijail_create":
55*bb4ee6a4SAndroid Build Coastguard Worker            addr_to_name[event["jail_addr"]] = event["name"]
56*bb4ee6a4SAndroid Build Coastguard Worker        elif event["event"] == "minijail_clone":
57*bb4ee6a4SAndroid Build Coastguard Worker            addr_to_name[event["dst_jail_addr"]] = addr_to_name[event["src_jail_addr"]]
58*bb4ee6a4SAndroid Build Coastguard Worker        elif event["event"] == "minijail_fork":
59*bb4ee6a4SAndroid Build Coastguard Worker            result.append((int(event["pid"]), addr_to_name[event["jail_addr"]]))
60*bb4ee6a4SAndroid Build Coastguard Worker        else:
61*bb4ee6a4SAndroid Build Coastguard Worker            raise ValueError("Unrecognized event type: {}".format(event["event"]))
62*bb4ee6a4SAndroid Build Coastguard Worker    return result
63*bb4ee6a4SAndroid Build Coastguard Worker
64*bb4ee6a4SAndroid Build Coastguard Worker
65*bb4ee6a4SAndroid Build Coastguard Workerbench = cmd("cargo test").with_color_flag()
66*bb4ee6a4SAndroid Build Coastguard Worker
67*bb4ee6a4SAndroid Build Coastguard Worker
68*bb4ee6a4SAndroid Build Coastguard Workerdef main(
69*bb4ee6a4SAndroid Build Coastguard Worker    target_name: str,
70*bb4ee6a4SAndroid Build Coastguard Worker    log_seccomp: bool = False,
71*bb4ee6a4SAndroid Build Coastguard Worker    log_seccomp_output_dir: str = "",
72*bb4ee6a4SAndroid Build Coastguard Worker    nocapture: bool = False,
73*bb4ee6a4SAndroid Build Coastguard Worker):
74*bb4ee6a4SAndroid Build Coastguard Worker    """Run an end-to-end benchmark target.
75*bb4ee6a4SAndroid Build Coastguard Worker
76*bb4ee6a4SAndroid Build Coastguard Worker    target-name -- name of target
77*bb4ee6a4SAndroid Build Coastguard Worker    log-seccomp -- record minijail seccomp filter with the run
78*bb4ee6a4SAndroid Build Coastguard Worker
79*bb4ee6a4SAndroid Build Coastguard Worker    """
80*bb4ee6a4SAndroid Build Coastguard Worker
81*bb4ee6a4SAndroid Build Coastguard Worker    if log_seccomp and not os.path.isdir(log_seccomp_output_dir):
82*bb4ee6a4SAndroid Build Coastguard Worker        raise ValueError("invalid log_seccomp_output_dir set")
83*bb4ee6a4SAndroid Build Coastguard Worker
84*bb4ee6a4SAndroid Build Coastguard Worker    if log_seccomp:
85*bb4ee6a4SAndroid Build Coastguard Worker        abs_seccomp_output_dir = pathlib.Path(log_seccomp_output_dir).absolute()
86*bb4ee6a4SAndroid Build Coastguard Worker
87*bb4ee6a4SAndroid Build Coastguard Worker    chdir(CROSVM_ROOT / "e2e_tests")
88*bb4ee6a4SAndroid Build Coastguard Worker
89*bb4ee6a4SAndroid Build Coastguard Worker    build_env = os.environ.copy()
90*bb4ee6a4SAndroid Build Coastguard Worker    build_env.update(Triple.host_default().get_cargo_env())
91*bb4ee6a4SAndroid Build Coastguard Worker
92*bb4ee6a4SAndroid Build Coastguard Worker    with tempfile.TemporaryDirectory() as tempdir:
93*bb4ee6a4SAndroid Build Coastguard Worker        if log_seccomp:
94*bb4ee6a4SAndroid Build Coastguard Worker            strace_out_path = pathlib.Path(tempdir) / "strace_out"
95*bb4ee6a4SAndroid Build Coastguard Worker            build_env.update(
96*bb4ee6a4SAndroid Build Coastguard Worker                {
97*bb4ee6a4SAndroid Build Coastguard Worker                    "CROSVM_CARGO_TEST_LOG_LEVEL_DEBUG": "1",
98*bb4ee6a4SAndroid Build Coastguard Worker                    "CROSVM_CARGO_TEST_E2E_WRAPPER_CMD": "strace -ff --output={}".format(
99*bb4ee6a4SAndroid Build Coastguard Worker                        os.path.abspath(strace_out_path)
100*bb4ee6a4SAndroid Build Coastguard Worker                    ),
101*bb4ee6a4SAndroid Build Coastguard Worker                    "CROSVM_CARGO_TEST_LOG_FILE": os.path.abspath(
102*bb4ee6a4SAndroid Build Coastguard Worker                        pathlib.Path(tempdir) / "crosvm.log"
103*bb4ee6a4SAndroid Build Coastguard Worker                    ),
104*bb4ee6a4SAndroid Build Coastguard Worker                }
105*bb4ee6a4SAndroid Build Coastguard Worker            )
106*bb4ee6a4SAndroid Build Coastguard Worker        if nocapture:
107*bb4ee6a4SAndroid Build Coastguard Worker            bench("--release", "--bench", target_name, "--", "--nocapture").with_envs(
108*bb4ee6a4SAndroid Build Coastguard Worker                build_env
109*bb4ee6a4SAndroid Build Coastguard Worker            ).fg()
110*bb4ee6a4SAndroid Build Coastguard Worker        else:
111*bb4ee6a4SAndroid Build Coastguard Worker            bench("--release", "--bench", target_name).with_envs(build_env).fg()
112*bb4ee6a4SAndroid Build Coastguard Worker
113*bb4ee6a4SAndroid Build Coastguard Worker        if log_seccomp:
114*bb4ee6a4SAndroid Build Coastguard Worker            with open(pathlib.Path(tempdir) / "crosvm.log", "r") as f:
115*bb4ee6a4SAndroid Build Coastguard Worker                pid_table = parse_seccomp_trace_events_to_pid_table(parse_crosvm_log(f.read()))
116*bb4ee6a4SAndroid Build Coastguard Worker
117*bb4ee6a4SAndroid Build Coastguard Worker            # Map each policy name to its frequency
118*bb4ee6a4SAndroid Build Coastguard Worker            policy_freq_dict: Dict[str, Counter[str]] = {}
119*bb4ee6a4SAndroid Build Coastguard Worker            for pid, policy_name in pid_table:
120*bb4ee6a4SAndroid Build Coastguard Worker                strace_log = pathlib.Path(
121*bb4ee6a4SAndroid Build Coastguard Worker                    os.path.normpath(strace_out_path) + f".{str(pid)}"
122*bb4ee6a4SAndroid Build Coastguard Worker                ).read_text()
123*bb4ee6a4SAndroid Build Coastguard Worker                freq_counter = parse_strace_file_str_to_freq_dict(strace_log)
124*bb4ee6a4SAndroid Build Coastguard Worker                if policy_name in policy_freq_dict:
125*bb4ee6a4SAndroid Build Coastguard Worker                    policy_freq_dict[policy_name] += freq_counter
126*bb4ee6a4SAndroid Build Coastguard Worker                else:
127*bb4ee6a4SAndroid Build Coastguard Worker                    policy_freq_dict[policy_name] = freq_counter
128*bb4ee6a4SAndroid Build Coastguard Worker
129*bb4ee6a4SAndroid Build Coastguard Worker            for policy_name, freq_counter in policy_freq_dict.items():
130*bb4ee6a4SAndroid Build Coastguard Worker                (abs_seccomp_output_dir / f"{policy_name}.frequency").write_text(
131*bb4ee6a4SAndroid Build Coastguard Worker                    freq_dict_to_freq_file_str(freq_counter)
132*bb4ee6a4SAndroid Build Coastguard Worker                )
133*bb4ee6a4SAndroid Build Coastguard Worker
134*bb4ee6a4SAndroid Build Coastguard Worker
135*bb4ee6a4SAndroid Build Coastguard Workerif __name__ == "__main__":
136*bb4ee6a4SAndroid Build Coastguard Worker    run_main(main)
137