run_local_memory.sh 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. # shellcheck source=scripts/lib/common.sh
  5. source "${scriptDir}/scripts/lib/common.sh"
  6. repoRoot="$(ryanjson_repo_root_from_source "${BASH_SOURCE[0]}" 0)"
  7. cd "${repoRoot}"
  8. memPlatform="${MEM_PLATFORM:-both}"
  9. memMode="${MEM_MODE:-full}"
  10. memStopOnFail="${MEM_STOP_ON_FAIL:-1}"
  11. memSingleCase="${MEM_SINGLE_CASE:-1}"
  12. memDefaultCase="${MEM_DEFAULT_CASE:-false false true}"
  13. memStripAnsiLog="${MEM_STRIP_ANSI_LOG:-1}"
  14. readonly memCases=(
  15. "header=12 align=8"
  16. "header=12 align=4"
  17. "header=8 align=8"
  18. "header=8 align=4"
  19. "header=4 align=4"
  20. )
  21. ryanjson_strip_ansi() {
  22. if command -v perl >/dev/null 2>&1; then
  23. perl -pe 's/\xEF\xBB\xBF//g; s/\x1b\[[0-9;]*[A-Za-z]//g'
  24. else
  25. sed -r 's/\xEF\xBB\xBF//g; s/\x1B\[[0-9;]*[A-Za-z]//g'
  26. fi
  27. }
  28. append_markdown_table_header() {
  29. local markdownPath="$1"
  30. local headerSize="$2"
  31. local alignSize="$3"
  32. local tableIndex="$4"
  33. if ((tableIndex > 0)); then
  34. printf '\n' >> "${markdownPath}"
  35. fi
  36. printf '### malloc 头部空间=%s 字节,对齐=%s 字节\n' "${headerSize}" "${alignSize}" >> "${markdownPath}"
  37. printf '| 用例 | 文本长度 | RyanJson 内存 | cJSON 内存 | yyjson 内存 | 相比 cJSON 节省%% | 相比 yyjson 节省%% |\n' >> "${markdownPath}"
  38. printf '| --- | --- | --- | --- | --- | --- | --- |\n' >> "${markdownPath}"
  39. }
  40. append_markdown_update_time() {
  41. local markdownPath="$1"
  42. local now=""
  43. now="$(date '+%Y-%m-%d %H:%M:%S')"
  44. printf '更新时间:%s\n\n' "${now}" >> "${markdownPath}"
  45. }
  46. append_markdown_title() {
  47. local markdownPath="$1"
  48. local platformLabel="$2"
  49. printf '# 内存占用对比(%s)\n\n' "${platformLabel}" >> "${markdownPath}"
  50. }
  51. append_markdown_note() {
  52. local markdownPath="$1"
  53. printf '说明:\n' >> "${markdownPath}"
  54. printf '%s\n' "- host 与 QEMU 结果可能不同:平台 ABI 与对齐规则差异会改变结构体布局与 padding。" >> "${markdownPath}"
  55. printf '%s\n' "- 即使同为 32 位,x86(i386) 与 ARM EABI 的 double/uint64_t 对齐也可能不同。" >> "${markdownPath}"
  56. printf '%s\n\n' "- 需要严格一致时,请以 QEMU 结果为准,或在同一 ABI/工具链下对比。" >> "${markdownPath}"
  57. }
  58. append_markdown_rows_from_stream() {
  59. local markdownPath="$1"
  60. awk '
  61. {
  62. gsub(/\r/, "", $0);
  63. }
  64. function pct(v) {
  65. if (v == "" || v == "NA") { return "-"; }
  66. return "**" v "%**";
  67. }
  68. function case_cn(id) {
  69. if (id == "mixed") { return "混合对象"; }
  70. if (id == "weather_object") { return "经典天气对象"; }
  71. if (id == "deep_array") { return "深度数组"; }
  72. if (id == "small_mixed") { return "小型混合对象"; }
  73. if (id == "small_string") { return "小型字符串对象"; }
  74. if (id == "compressed_business") { return "压缩业务对象"; }
  75. return id;
  76. }
  77. /^\[MEM\]\[COMPARE\]/ {
  78. id = ""; len = ""; r = ""; c = ""; y = "";
  79. for (i = 2; i <= NF; i++) {
  80. split($i, kv, "=");
  81. key = kv[1]; val = kv[2];
  82. if (key == "id") id = val;
  83. else if (key == "len") len = val;
  84. else if (key == "ryanjson") r = val;
  85. else if (key == "cjson") c = val;
  86. else if (key == "yyjson") y = val;
  87. }
  88. if (id == "") { next; }
  89. saveC = "NA"; saveY = "NA";
  90. if ((c + 0) > 0 && (r + 0) > 0) { saveC = sprintf("%.2f", 100 - (r * 100 / c)); }
  91. if ((y + 0) > 0 && (r + 0) > 0) { saveY = sprintf("%.2f", 100 - (r * 100 / y)); }
  92. printf "| %s | %s | %s | %s | %s | %s | %s |\n", \
  93. case_cn(id), len, r, c, y, pct(saveC), pct(saveY);
  94. }
  95. ' >> "${markdownPath}"
  96. }
  97. run_and_capture_rows() {
  98. local markdownPath="$1"
  99. local stripAnsi="$2"
  100. shift 2
  101. local tmpRoot="${repoRoot}/localLogs/_tmp"
  102. local fifoPath="${tmpRoot}/memory_stream.$$"
  103. local cmdRc=0
  104. local parserPid=0
  105. mkdir -p "${tmpRoot}"
  106. rm -f "${fifoPath}"
  107. mkfifo "${fifoPath}"
  108. append_markdown_rows_from_stream "${markdownPath}" < "${fifoPath}" &
  109. parserPid=$!
  110. if [[ "${stripAnsi}" == "1" ]]; then
  111. "$@" 2>&1 | ryanjson_strip_ansi | tee "${fifoPath}"
  112. else
  113. "$@" 2>&1 | tee "${fifoPath}"
  114. fi
  115. cmdRc=${PIPESTATUS[0]}
  116. wait "${parserPid}"
  117. rm -f "${fifoPath}"
  118. return "${cmdRc}"
  119. }
  120. validate_inputs() {
  121. if [[ "${memPlatform}" != "host" && "${memPlatform}" != "qemu" && "${memPlatform}" != "both" ]]; then
  122. ryanjson_log_error "MEM_PLATFORM only supports host/qemu/both, current: ${memPlatform}"
  123. return 1
  124. fi
  125. if ! ryanjson_require_01 "MEM_STOP_ON_FAIL" "${memStopOnFail}"; then
  126. return 1
  127. fi
  128. if ! ryanjson_require_01 "MEM_STRIP_ANSI_LOG" "${memStripAnsiLog}"; then
  129. return 1
  130. fi
  131. if ! ryanjson_require_01 "MEM_SINGLE_CASE" "${memSingleCase}"; then
  132. return 1
  133. fi
  134. if [[ "${memSingleCase}" == "0" ]]; then
  135. if ! ryanjson_emit_semantic_cases "${memMode}" "MEM_MODE" >/dev/null; then
  136. return 1
  137. fi
  138. fi
  139. }
  140. print_banner() {
  141. ryanjson_print_banner_begin "Local Memory Compare"
  142. ryanjson_print_banner_kv "MEM_PLATFORM" "${memPlatform}"
  143. ryanjson_print_banner_kv "MEM_MODE" "${memMode}"
  144. ryanjson_print_banner_kv "MEM_STOP_ON_FAIL" "${memStopOnFail}"
  145. ryanjson_print_banner_kv "MEM_SINGLE_CASE" "${memSingleCase}"
  146. ryanjson_print_banner_kv_optional "MEM_DEFAULT_CASE" "${memDefaultCase}"
  147. ryanjson_print_banner_kv "MEM_STRIP_ANSI_LOG" "${memStripAnsiLog}"
  148. ryanjson_print_banner_end
  149. }
  150. run_host_memory() {
  151. local markdownPath="${repoRoot}/reports/memory/host.md"
  152. local rc=0
  153. local caseRc=0
  154. local entry=""
  155. local headerSize=0
  156. local alignSize=0
  157. local caseLabel=""
  158. local tableIndex=0
  159. local headerToken=""
  160. local alignToken=""
  161. ryanjson_init_utf8_log "${markdownPath}"
  162. append_markdown_title "${markdownPath}" "Host"
  163. append_markdown_update_time "${markdownPath}"
  164. append_markdown_note "${markdownPath}"
  165. for entry in "${memCases[@]}"; do
  166. read -r headerToken alignToken <<< "${entry}"
  167. headerSize="${headerToken#header=}"
  168. alignSize="${alignToken#align=}"
  169. caseLabel="h${headerSize}_a${alignSize}"
  170. append_markdown_table_header "${markdownPath}" "${headerSize}" "${alignSize}" "${tableIndex}"
  171. tableIndex=$((tableIndex + 1))
  172. export RYANJSON_TEST_ALLOC_HEADER_SIZE="${headerSize}"
  173. export RYANJSON_TEST_ALLOC_ALIGN_SIZE="${alignSize}"
  174. export RYANJSON_UNIT_ONLY_MEMORY=1
  175. export UNIT_MODE="${memMode}"
  176. export UNIT_SKIP_COV=1
  177. export UNIT_STOP_ON_FAIL="${memStopOnFail}"
  178. export XMAKE_FORCE_CLEAN="${XMAKE_FORCE_CLEAN:-0}"
  179. export UNIT_SYNC_ONLY="${UNIT_SYNC_ONLY:-0}"
  180. if [[ "${memSingleCase}" == "1" ]]; then
  181. export UNIT_SINGLE_CASE="${memDefaultCase}"
  182. else
  183. unset UNIT_SINGLE_CASE
  184. fi
  185. set +e
  186. run_and_capture_rows "${markdownPath}" "${memStripAnsiLog}" \
  187. bash "${repoRoot}/run_local_base.sh"
  188. caseRc=$?
  189. set -e
  190. if [[ "${caseRc}" -ne 0 ]]; then
  191. rc="${caseRc}"
  192. if [[ "${memStopOnFail}" == "1" ]]; then
  193. break
  194. fi
  195. fi
  196. done
  197. ryanjson_log_done "Memory summary (Markdown): ${markdownPath}"
  198. cat "${markdownPath}"
  199. return "${rc}"
  200. }
  201. run_qemu_memory() {
  202. local markdownPath="${repoRoot}/reports/memory/qemu.md"
  203. local rc=0
  204. local caseRc=0
  205. local entry=""
  206. local headerSize=0
  207. local alignSize=0
  208. local caseLabel=""
  209. local tableIndex=0
  210. local headerToken=""
  211. local alignToken=""
  212. ryanjson_init_utf8_log "${markdownPath}"
  213. append_markdown_title "${markdownPath}" "QEMU"
  214. append_markdown_update_time "${markdownPath}"
  215. append_markdown_note "${markdownPath}"
  216. for entry in "${memCases[@]}"; do
  217. read -r headerToken alignToken <<< "${entry}"
  218. headerSize="${headerToken#header=}"
  219. alignSize="${alignToken#align=}"
  220. caseLabel="h${headerSize}_a${alignSize}"
  221. append_markdown_table_header "${markdownPath}" "${headerSize}" "${alignSize}" "${tableIndex}"
  222. tableIndex=$((tableIndex + 1))
  223. export RYANJSON_TEST_ALLOC_HEADER_SIZE="${headerSize}"
  224. export RYANJSON_TEST_ALLOC_ALIGN_SIZE="${alignSize}"
  225. export RYANJSON_UNIT_ONLY_MEMORY=1
  226. export QEMU_MODE="${memMode}"
  227. export QEMU_STOP_ON_FAIL="${memStopOnFail}"
  228. export QEMU_SAVE_LOG=0
  229. export QEMU_CONSOLE_LOG=1
  230. export QEMU_LOG_ROOT="localLogs/qemu/memory/${caseLabel}"
  231. export QEMU_STRIP_ANSI_LOG="${memStripAnsiLog}"
  232. if [[ "${memSingleCase}" == "1" ]]; then
  233. export QEMU_SINGLE_CASE="${memDefaultCase}"
  234. else
  235. unset QEMU_SINGLE_CASE
  236. fi
  237. set +e
  238. run_and_capture_rows "${markdownPath}" "${memStripAnsiLog}" \
  239. bash "${repoRoot}/run_local_qemu.sh"
  240. caseRc=$?
  241. set -e
  242. if [[ "${caseRc}" -ne 0 ]]; then
  243. rc="${caseRc}"
  244. if [[ "${memStopOnFail}" == "1" ]]; then
  245. break
  246. fi
  247. fi
  248. done
  249. ryanjson_log_done "Memory summary (Markdown): ${markdownPath}"
  250. cat "${markdownPath}"
  251. if [[ "${rc}" -ne 0 ]]; then
  252. return "${rc}"
  253. fi
  254. return 0
  255. }
  256. main() {
  257. validate_inputs
  258. print_banner
  259. case "${memPlatform}" in
  260. host)
  261. run_host_memory
  262. ;;
  263. qemu)
  264. run_qemu_memory
  265. ;;
  266. both)
  267. local hostRc=0
  268. local qemuRc=0
  269. if run_host_memory; then
  270. hostRc=0
  271. else
  272. hostRc=$?
  273. fi
  274. if run_qemu_memory; then
  275. qemuRc=0
  276. else
  277. qemuRc=$?
  278. fi
  279. if [[ "${hostRc}" -ne 0 || "${qemuRc}" -ne 0 ]]; then
  280. return 1
  281. fi
  282. ;;
  283. esac
  284. }
  285. main "$@"