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(target_name: str, log_seccomp: bool = False, log_seccomp_output_dir: str = ""): 69 """Run an end-to-end benchmark target. 70 71 target-name -- name of target 72 log-seccomp -- record minijail seccomp filter with the run 73 74 """ 75 76 if log_seccomp and not os.path.isdir(log_seccomp_output_dir): 77 raise ValueError("invalid log_seccomp_output_dir set") 78 79 if log_seccomp: 80 abs_seccomp_output_dir = pathlib.Path(log_seccomp_output_dir).absolute() 81 82 chdir(CROSVM_ROOT / "e2e_tests") 83 84 build_env = os.environ.copy() 85 build_env.update(Triple.host_default().get_cargo_env()) 86 87 with tempfile.TemporaryDirectory() as tempdir: 88 if log_seccomp: 89 strace_out_path = pathlib.Path(tempdir) / "strace_out" 90 build_env.update( 91 { 92 "CROSVM_CARGO_TEST_LOG_LEVEL_DEBUG": "1", 93 "CROSVM_CARGO_TEST_E2E_WRAPPER_CMD": "strace -ff --output={}".format( 94 os.path.abspath(strace_out_path) 95 ), 96 "CROSVM_CARGO_TEST_LOG_FILE": os.path.abspath( 97 pathlib.Path(tempdir) / "crosvm.log" 98 ), 99 } 100 ) 101 102 bench("--release", "--bench", target_name).with_envs(build_env).fg() 103 104 if log_seccomp: 105 with open(pathlib.Path(tempdir) / "crosvm.log", "r") as f: 106 pid_table = parse_seccomp_trace_events_to_pid_table(parse_crosvm_log(f.read())) 107 108 # Map each policy name to its frequency 109 policy_freq_dict: Dict[str, Counter[str]] = {} 110 for pid, policy_name in pid_table: 111 strace_log = pathlib.Path( 112 os.path.normpath(strace_out_path) + f".{str(pid)}" 113 ).read_text() 114 freq_counter = parse_strace_file_str_to_freq_dict(strace_log) 115 if policy_name in policy_freq_dict: 116 policy_freq_dict[policy_name] += freq_counter 117 else: 118 policy_freq_dict[policy_name] = freq_counter 119 120 for policy_name, freq_counter in policy_freq_dict.items(): 121 (abs_seccomp_output_dir / f"{policy_name}.frequency").write_text( 122 freq_dict_to_freq_file_str(freq_counter) 123 ) 124 125 126if __name__ == "__main__": 127 run_main(main) 128