crosvm/tools/bench
Zihan Chen 8f5225c042 e2e_tests: Allow TestVM to run with custom artifacts
TestVM can now be configured to run on specific local or remote
kernel, initrd and rootfs images. This allow us to broaden e2e test
coverages to more diverse use cases.

TESTED=Relevant test case passed CQ and locally

BUG=b:257303497
BUG=b:181105093

Change-Id: I1270a189105af89259371f54b7df227b3efae380
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4451468
Reviewed-by: Dennis Kempin <denniskempin@google.com>
Commit-Queue: Zihan Chen <zihanchen@google.com>
2023-06-14 22:22:38 +00:00

136 lines
5.3 KiB
Python
Executable file

#!/usr/bin/env python3
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import os
import pathlib
import re
import tempfile
from collections import Counter
from typing import Dict, List, Tuple
from impl.common import CROSVM_ROOT, Triple, chdir, cmd, run_main
# Capture syscall name as group 1 from strace log
# E.g. 'access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)' will have 'access' as group 1
syscall_re = re.compile(r"^(\w+)\(.+?= .+$", re.IGNORECASE | re.MULTILINE)
# Capture seccomp_trace json as group 1 from crosvm log
# E.g. ' DEBUG jail::helpers] seccomp_trace {"event": "minijail_create", "name": "balloon_device", "jail_addr": "0x55623bea1df0"}'
# will have the entire json string as group 1
seccomp_trace_log_re = re.compile(r"DEBUG.*? seccomp_trace .*?(\{.*\}).*?$", re.MULTILINE)
def parse_strace_file_str_to_freq_dict(strace_content: str) -> Counter[str]:
# TODO: start from after seccomp
# TODO: throw exception when seccomp isn't detected
return Counter(map(lambda m: m.group(1), syscall_re.finditer(strace_content)))
def freq_dict_to_freq_file_str(freq_dict: Dict[str, int]) -> str:
return "\n".join(map(lambda k: f"{k}: {str(freq_dict[k])}", sorted(freq_dict.keys())))
def parse_crosvm_log(crosvm_log: str) -> List[Dict[str, str]]:
# Load each seccomp_trace event json as dict
seccomp_trace_events = []
for match in seccomp_trace_log_re.finditer(crosvm_log):
seccomp_trace_events.append(json.loads(match.group(1)))
return seccomp_trace_events
def parse_seccomp_trace_events_to_pid_table(
seccomp_trace_events: List[Dict[str, str]]
) -> List[Tuple[int, str]]:
# There are 3 types of minijail events: create, clone, form
# 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.
# 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.
# Fork is when a jail is enabled and a process is forked to be executed inside the jail.
addr_to_name: Dict[str, str] = dict()
result = []
for event in seccomp_trace_events:
if event["event"] == "minijail_create":
addr_to_name[event["jail_addr"]] = event["name"]
elif event["event"] == "minijail_clone":
addr_to_name[event["dst_jail_addr"]] = addr_to_name[event["src_jail_addr"]]
elif event["event"] == "minijail_fork":
result.append((int(event["pid"]), addr_to_name[event["jail_addr"]]))
else:
raise ValueError("Unrecognized event type: {}".format(event["event"]))
return result
bench = cmd("cargo test").with_color_flag()
def main(
target_name: str,
log_seccomp: bool = False,
log_seccomp_output_dir: str = "",
nocapture: bool = False,
):
"""Run an end-to-end benchmark target.
target-name -- name of target
log-seccomp -- record minijail seccomp filter with the run
"""
if log_seccomp and not os.path.isdir(log_seccomp_output_dir):
raise ValueError("invalid log_seccomp_output_dir set")
if log_seccomp:
abs_seccomp_output_dir = pathlib.Path(log_seccomp_output_dir).absolute()
chdir(CROSVM_ROOT / "e2e_tests")
build_env = os.environ.copy()
build_env.update(Triple.host_default().get_cargo_env())
with tempfile.TemporaryDirectory() as tempdir:
if log_seccomp:
strace_out_path = pathlib.Path(tempdir) / "strace_out"
build_env.update(
{
"CROSVM_CARGO_TEST_LOG_LEVEL_DEBUG": "1",
"CROSVM_CARGO_TEST_E2E_WRAPPER_CMD": "strace -ff --output={}".format(
os.path.abspath(strace_out_path)
),
"CROSVM_CARGO_TEST_LOG_FILE": os.path.abspath(
pathlib.Path(tempdir) / "crosvm.log"
),
}
)
if nocapture:
bench("--release", "--bench", target_name, "--", "--nocapture").with_envs(
build_env
).fg()
else:
bench("--release", "--bench", target_name).with_envs(build_env).fg()
if log_seccomp:
with open(pathlib.Path(tempdir) / "crosvm.log", "r") as f:
pid_table = parse_seccomp_trace_events_to_pid_table(parse_crosvm_log(f.read()))
# Map each policy name to its frequency
policy_freq_dict: Dict[str, Counter[str]] = {}
for pid, policy_name in pid_table:
strace_log = pathlib.Path(
os.path.normpath(strace_out_path) + f".{str(pid)}"
).read_text()
freq_counter = parse_strace_file_str_to_freq_dict(strace_log)
if policy_name in policy_freq_dict:
policy_freq_dict[policy_name] += freq_counter
else:
policy_freq_dict[policy_name] = freq_counter
for policy_name, freq_counter in policy_freq_dict.items():
(abs_seccomp_output_dir / f"{policy_name}.frequency").write_text(
freq_dict_to_freq_file_str(freq_counter)
)
if __name__ == "__main__":
run_main(main)