• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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