trans_wasm_func_name.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (C) 2019 Intel Corporation. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. """
  7. It is used to translate jitted functions' names(in out.folded) to coorespond name in name section in .wasm
  8. Usage:
  9. After
  10. ```
  11. $ perf script -i perf.data > out.perf
  12. # fold call stacks
  13. $ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
  14. ```
  15. Add a step:
  16. ```
  17. # translate jitted functions' names
  18. $ python translate_wasm_function_name.py --wabt_home <wabt-installation> --folded out.folded <.wasm>
  19. # out.folded -> out.folded.translated
  20. $ ls out.folded.translated
  21. ```
  22. Then
  23. ```
  24. # generate flamegraph
  25. $ ./FlameGraph/flamegraph.pl out.folded.translated > perf.wasm.svg
  26. ```
  27. """
  28. import argparse
  29. import os
  30. from pathlib import Path
  31. import re
  32. import shlex
  33. import subprocess
  34. def preflight_check(wabt_home: Path) -> Path:
  35. """
  36. if wasm-objdump exists in wabt_home
  37. """
  38. wasm_objdump_bin = wabt_home.joinpath("bin", "wasm-objdump")
  39. if not wasm_objdump_bin.exists():
  40. raise RuntimeError(f"wasm-objdump not found in {wabt_home}")
  41. return wasm_objdump_bin
  42. def collect_import_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict:
  43. """
  44. execute "wasm_objdump_bin -j Import -x <wasm_file>" and return a dict like {function: X, global: Y, memory: Z, table: N}
  45. """
  46. assert wasm_objdump_bin.exists()
  47. assert wasm_file.exists()
  48. command = f"{wasm_objdump_bin} -j Import -x {wasm_file}"
  49. p = subprocess.run(
  50. shlex.split(command),
  51. capture_output=True,
  52. check=False,
  53. text=True,
  54. universal_newlines=True,
  55. )
  56. if p.stderr:
  57. return {}
  58. import_section = {}
  59. for line in p.stdout.split(os.linesep):
  60. line = line.strip()
  61. if not line:
  62. continue
  63. if line.startswith(" - func"):
  64. import_section.update("function", import_section.get("function", 0) + 1)
  65. else:
  66. pass
  67. return import_section
  68. def collect_name_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict:
  69. """
  70. execute "wasm_objdump_bin -j name -x wasm_file" and store the output in a list
  71. """
  72. assert wasm_objdump_bin.exists()
  73. assert wasm_file.exists()
  74. command = f"{wasm_objdump_bin} -j name -x {wasm_file}"
  75. p = subprocess.run(
  76. shlex.split(command),
  77. capture_output=True,
  78. check=False,
  79. text=True,
  80. universal_newlines=True,
  81. )
  82. if p.stderr:
  83. raise RuntimeError(f"not found name section in {wasm_file}")
  84. name_section = {}
  85. for line in p.stdout.split(os.linesep):
  86. line = line.strip()
  87. if not line:
  88. continue
  89. # - func[0] <__imported_wasi_snapshot_preview1_fd_close>
  90. if line.startswith("- func"):
  91. m = re.match(r"- func\[(\d+)\] <(.+)>", line)
  92. assert m
  93. func_index, func_name = m.groups()
  94. name_section.update({func_index: func_name})
  95. assert name_section
  96. return name_section
  97. def replace_function_name(
  98. import_section: dict, name_section: dict, folded_in: str, folded_out: str
  99. ) -> None:
  100. """
  101. read content in <folded_in>. each line will be like:
  102. quiche::BalsaFrame::ProcessHeaders;non-virtual thunk to Envoy::Http::Http1::BalsaParser::MessageDone;Envoy::Http::Http1::ConnectionImpl::onMessageComplete;Envoy::Http::Http1::ConnectionImpl::onMessageCompleteImpl;Envoy::Http::Http1::ServerConnectionImpl::onMessageCompleteBase;Envoy::Http::ConnectionManagerImpl::ActiveStream::decodeHeaders;Envoy::Http::FilterManager::decodeHeaders;virtual thunk to Envoy::Extensions::Common::Wasm::Context::decodeHeaders;proxy_wasm::ContextBase::onRequestHeaders;proxy_wasm::wamr::Wamr::getModuleFunctionImpl<proxy_wasm::Word, proxy_wasm::Word, proxy_wasm::Word, proxy_wasm::Word>;wasm_func_call;wasm_runtime_call_wasm;wasm_call_function;call_wasm_with_hw_bound_check;wasm_interp_call_wasm;llvm_jit_call_func_bytecode;wasm_runtime_invoke_native;push_args_end;aot_func_internal#3302;aot_func_internal#3308;asm_sysvec_apic_timer_interrupt;sysvec_apic_timer_interrupt;__sysvec_apic_timer_interrupt;hrtimer_interrupt;__hrtimer_run_queues;__remove_hrtimer;rb_next 1110899
  103. symbol names are spearated by ";"
  104. if there is a symbol named like "aot_func#XXX" or "aot_func_internal#XXX", it will be replaced with the function name in name section by index
  105. """
  106. folded_in = Path(folded_in)
  107. assert folded_in.exists()
  108. folded_out = Path(folded_out)
  109. import_function_count = import_section.get("function", 0)
  110. with folded_in.open("rt", encoding="utf-8") as f_in, folded_out.open(
  111. "wt", encoding="utf-8"
  112. ) as f_out:
  113. precheck_mode = False
  114. for line in f_in:
  115. line = line.strip()
  116. if "aot_func_internal" in line:
  117. precheck_mode = True
  118. f_in.seek(0)
  119. for line in f_in:
  120. new_line = []
  121. line = line.strip()
  122. m = re.match(r"(.*) (\d+)", line)
  123. syms, samples = m.groups()
  124. for sym in syms.split(";"):
  125. m = re.match(r"aot_func(_internal)?#(\d+)", sym)
  126. if not m:
  127. new_line.append(sym)
  128. continue
  129. func_idx = m.groups()[-1]
  130. if func_idx in name_section:
  131. wasm_func_name = f"[Wasm] {name_section[func_idx]}"
  132. else:
  133. wasm_func_name = (
  134. f"[Wasm] function[{func_idx + import_function_count}]"
  135. )
  136. if precheck_mode:
  137. # aot_func_internal -> xxx
  138. # aot_func --> xxx_precheck
  139. wasm_func_name += "_precheck" if not m.groups()[0] else ""
  140. else:
  141. # aot_func --> xxx
  142. pass
  143. new_line.append(wasm_func_name)
  144. line = ";".join(new_line)
  145. line += f" {samples}"
  146. f_out.write(line + os.linesep)
  147. print(f"⚙️ {folded_in} -> {folded_out}")
  148. def main(wabt_home: str, wasm_file: str, folded: str) -> None:
  149. wabt_home = Path(wabt_home)
  150. wasm_file = Path(wasm_file)
  151. wasm_objdump_bin = preflight_check(wabt_home)
  152. import_section = collect_import_section_content(wasm_objdump_bin, wasm_file)
  153. name_section = collect_name_section_content(wasm_objdump_bin, wasm_file)
  154. replace_function_name(import_section, name_section, folded, folded + ".translated")
  155. if __name__ == "__main__":
  156. argparse = argparse.ArgumentParser()
  157. argparse.add_argument(
  158. "--folded", help="stackcollapse-perf.pl generated, like out.folded"
  159. )
  160. argparse.add_argument("wasm_file", help="wasm file")
  161. argparse.add_argument("--wabt_home", help="wabt home, like /opt/wabt-1.0.33")
  162. args = argparse.parse_args()
  163. main(args.wabt_home, args.wasm_file, args.folded)