bench 5.3 KB

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