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