Enhance wasm loading with LoadArgs and support module names (#3265)

- Add new API wasm_runtime_load_ex() in wasm_export.h
  and wasm_module_new_ex in wasm_c_api.h
- Put aot_create_perf_map() into a separated file aot_perf_map.c
- In perf.map, function names include user specified module name
- Enhance the script to help flamegraph generations
This commit is contained in:
liang.he 2024-04-07 15:04:35 +08:00 committed by GitHub
parent cee9b826a5
commit 4ef724bbff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 3008 additions and 346 deletions

View File

@ -16,6 +16,10 @@
#include "debug/jit_debug.h"
#endif
#if WASM_ENABLE_LINUX_PERF != 0
#include "aot_perf_map.h"
#endif
#define YMM_PLT_PREFIX "__ymm@"
#define XMM_PLT_PREFIX "__xmm@"
#define REAL_PLT_PREFIX "__real@"
@ -3601,104 +3605,6 @@ fail:
return ret;
}
#if WASM_ENABLE_LINUX_PERF != 0
struct func_info {
uint32 idx;
void *ptr;
};
static uint32
get_func_size(const AOTModule *module, struct func_info *sorted_func_ptrs,
uint32 idx)
{
uint32 func_sz;
if (idx == module->func_count - 1)
func_sz = (uintptr_t)module->code + module->code_size
- (uintptr_t)(sorted_func_ptrs[idx].ptr);
else
func_sz = (uintptr_t)(sorted_func_ptrs[idx + 1].ptr)
- (uintptr_t)(sorted_func_ptrs[idx].ptr);
return func_sz;
}
static int
compare_func_ptrs(const void *f1, const void *f2)
{
return (intptr_t)((struct func_info *)f1)->ptr
- (intptr_t)((struct func_info *)f2)->ptr;
}
static struct func_info *
sort_func_ptrs(const AOTModule *module, char *error_buf, uint32 error_buf_size)
{
uint64 content_len;
struct func_info *sorted_func_ptrs;
unsigned i;
content_len = (uint64)sizeof(struct func_info) * module->func_count;
sorted_func_ptrs = loader_malloc(content_len, error_buf, error_buf_size);
if (!sorted_func_ptrs)
return NULL;
for (i = 0; i < module->func_count; i++) {
sorted_func_ptrs[i].idx = i;
sorted_func_ptrs[i].ptr = module->func_ptrs[i];
}
qsort(sorted_func_ptrs, module->func_count, sizeof(struct func_info),
compare_func_ptrs);
return sorted_func_ptrs;
}
static bool
create_perf_map(const AOTModule *module, char *error_buf, uint32 error_buf_size)
{
struct func_info *sorted_func_ptrs = NULL;
char perf_map_info[128] = { 0 };
FILE *perf_map = NULL;
uint32 i;
pid_t pid = getpid();
bool ret = false;
sorted_func_ptrs = sort_func_ptrs(module, error_buf, error_buf_size);
if (!sorted_func_ptrs)
goto quit;
snprintf(perf_map_info, 128, "/tmp/perf-%d.map", pid);
perf_map = fopen(perf_map_info, "w");
if (!perf_map) {
LOG_WARNING("warning: can't create /tmp/perf-%d.map, because %s", pid,
strerror(errno));
goto quit;
}
for (i = 0; i < module->func_count; i++) {
memset(perf_map_info, 0, 128);
snprintf(perf_map_info, 128, "%lx %x aot_func#%u\n",
(uintptr_t)sorted_func_ptrs[i].ptr,
get_func_size(module, sorted_func_ptrs, i),
sorted_func_ptrs[i].idx);
fwrite(perf_map_info, 1, strlen(perf_map_info), perf_map);
}
LOG_VERBOSE("generate /tmp/perf-%d.map", pid);
ret = true;
quit:
if (sorted_func_ptrs)
free(sorted_func_ptrs);
if (perf_map)
fclose(perf_map);
return ret;
}
#endif /* WASM_ENABLE_LINUX_PERF != 0*/
static bool
load_from_sections(AOTModule *module, AOTSection *sections,
bool is_load_from_file_buf, char *error_buf,
@ -3889,7 +3795,7 @@ load_from_sections(AOTModule *module, AOTSection *sections,
}
static AOTModule *
create_module(char *error_buf, uint32 error_buf_size)
create_module(char *name, char *error_buf, uint32 error_buf_size)
{
AOTModule *module =
loader_malloc(sizeof(AOTModule), error_buf, error_buf_size);
@ -3901,7 +3807,7 @@ create_module(char *error_buf, uint32 error_buf_size)
module->module_type = Wasm_Module_AoT;
module->name = "";
module->name = name;
#if WASM_ENABLE_MULTI_MODULE != 0
module->import_module_list = &module->import_module_list_head;
@ -3937,7 +3843,7 @@ AOTModule *
aot_load_from_sections(AOTSection *section_list, char *error_buf,
uint32 error_buf_size)
{
AOTModule *module = create_module(error_buf, error_buf_size);
AOTModule *module = create_module("", error_buf, error_buf_size);
if (!module)
return NULL;
@ -4183,7 +4089,7 @@ load(const uint8 *buf, uint32 size, AOTModule *module, char *error_buf,
#if WASM_ENABLE_LINUX_PERF != 0
if (wasm_runtime_get_linux_perf())
if (!create_perf_map(module, error_buf, error_buf_size))
if (!aot_create_perf_map(module, error_buf, error_buf_size))
goto fail;
#endif
@ -4193,10 +4099,10 @@ fail:
}
AOTModule *
aot_load_from_aot_file(const uint8 *buf, uint32 size, char *error_buf,
uint32 error_buf_size)
aot_load_from_aot_file(const uint8 *buf, uint32 size, const LoadArgs *args,
char *error_buf, uint32 error_buf_size)
{
AOTModule *module = create_module(error_buf, error_buf_size);
AOTModule *module = create_module(args->name, error_buf, error_buf_size);
if (!module)
return NULL;

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include "aot_perf_map.h"
#include "bh_log.h"
#include "bh_platform.h"
#if WASM_ENABLE_LINUX_PERF != 0
struct func_info {
uint32 idx;
void *ptr;
};
static uint32
get_func_size(const AOTModule *module, struct func_info *sorted_func_ptrs,
uint32 idx)
{
uint32 func_sz;
if (idx == module->func_count - 1)
func_sz = (uintptr_t)module->code + module->code_size
- (uintptr_t)(sorted_func_ptrs[idx].ptr);
else
func_sz = (uintptr_t)(sorted_func_ptrs[idx + 1].ptr)
- (uintptr_t)(sorted_func_ptrs[idx].ptr);
return func_sz;
}
static int
compare_func_ptrs(const void *f1, const void *f2)
{
return (intptr_t)((struct func_info *)f1)->ptr
- (intptr_t)((struct func_info *)f2)->ptr;
}
static struct func_info *
sort_func_ptrs(const AOTModule *module, char *error_buf, uint32 error_buf_size)
{
uint64 content_len;
struct func_info *sorted_func_ptrs;
unsigned i;
content_len = (uint64)sizeof(struct func_info) * module->func_count;
sorted_func_ptrs = wasm_runtime_malloc(content_len);
if (!sorted_func_ptrs) {
snprintf(error_buf, error_buf_size,
"allocate memory failed when creating perf map");
return NULL;
}
for (i = 0; i < module->func_count; i++) {
sorted_func_ptrs[i].idx = i;
sorted_func_ptrs[i].ptr = module->func_ptrs[i];
}
qsort(sorted_func_ptrs, module->func_count, sizeof(struct func_info),
compare_func_ptrs);
return sorted_func_ptrs;
}
bool
aot_create_perf_map(const AOTModule *module, char *error_buf,
uint32 error_buf_size)
{
struct func_info *sorted_func_ptrs = NULL;
char perf_map_path[64] = { 0 };
char perf_map_info[128] = { 0 };
FILE *perf_map = NULL;
uint32 i;
pid_t pid = getpid();
bool ret = false;
sorted_func_ptrs = sort_func_ptrs(module, error_buf, error_buf_size);
if (!sorted_func_ptrs)
goto quit;
snprintf(perf_map_path, sizeof(perf_map_path) - 1, "/tmp/perf-%d.map", pid);
perf_map = fopen(perf_map_path, "a");
if (!perf_map) {
LOG_WARNING("warning: can't create /tmp/perf-%d.map, because %s", pid,
strerror(errno));
goto quit;
}
const char *module_name = aot_get_module_name((AOTModule *)module);
for (i = 0; i < module->func_count; i++) {
memset(perf_map_info, 0, 128);
if (strlen(module_name) > 0)
snprintf(perf_map_info, 128, "%lx %x [%s]#aot_func#%u\n",
(uintptr_t)sorted_func_ptrs[i].ptr,
get_func_size(module, sorted_func_ptrs, i), module_name,
sorted_func_ptrs[i].idx);
else
snprintf(perf_map_info, 128, "%lx %x aot_func#%u\n",
(uintptr_t)sorted_func_ptrs[i].ptr,
get_func_size(module, sorted_func_ptrs, i),
sorted_func_ptrs[i].idx);
/* fwrite() is thread safe */
fwrite(perf_map_info, 1, strlen(perf_map_info), perf_map);
}
LOG_VERBOSE("write map information from %s into /tmp/perf-%d.map",
module_name, pid);
ret = true;
quit:
if (sorted_func_ptrs)
wasm_runtime_free(sorted_func_ptrs);
if (perf_map)
fclose(perf_map);
return ret;
}
#endif /* WASM_ENABLE_LINUX_PERF != 0 */

View File

@ -0,0 +1,15 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#ifndef _AOT_PERF_MAP_H_
#define _AOT_PERF_MAP_H_
#include "aot_runtime.h"
bool
aot_create_perf_map(const AOTModule *module, char *error_buf,
uint32 error_buf_size);
#endif /* _AOT_PERF_MAP_H_ */

View File

@ -444,8 +444,8 @@ typedef struct LLVMProfileData_64 {
* @return return AOT module loaded, NULL if failed
*/
AOTModule *
aot_load_from_aot_file(const uint8 *buf, uint32 size, char *error_buf,
uint32 error_buf_size);
aot_load_from_aot_file(const uint8 *buf, uint32 size, const LoadArgs *args,
char *error_buf, uint32 error_buf_size);
/**
* Load a AOT module from a specified AOT section list.

View File

@ -2234,7 +2234,8 @@ quit:
#endif /* WASM_ENABLE_WASM_CACHE != 0 */
wasm_module_t *
wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary)
wasm_module_new_ex(wasm_store_t *store, const wasm_byte_vec_t *binary,
const LoadArgs *args)
{
char error_buf[128] = { 0 };
wasm_module_ex_t *module_ex = NULL;
@ -2290,8 +2291,8 @@ wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary)
if (!module_ex->binary->data)
goto free_binary;
module_ex->module_comm_rt = wasm_runtime_load(
(uint8 *)module_ex->binary->data, (uint32)module_ex->binary->size,
module_ex->module_comm_rt = wasm_runtime_load_ex(
(uint8 *)module_ex->binary->data, (uint32)module_ex->binary->size, args,
error_buf, (uint32)sizeof(error_buf));
if (!(module_ex->module_comm_rt)) {
LOG_ERROR("%s", error_buf);
@ -2337,6 +2338,14 @@ quit:
return NULL;
}
wasm_module_t *
wasm_module_new(wasm_store_t *store, const wasm_byte_vec_t *binary)
{
LoadArgs args = { 0 };
args.name = "";
return wasm_module_new_ex(store, binary, &args);
}
bool
wasm_module_validate(wasm_store_t *store, const wasm_byte_vec_t *binary)
{

View File

@ -65,7 +65,7 @@
#if WASM_ENABLE_MULTI_MODULE != 0
/**
* A safety insurance to prevent
* circular depencies which leads stack overflow
* circular dependencies which leads stack overflow
* try to break early
*/
typedef struct LoadingModule {
@ -1333,11 +1333,15 @@ register_module_with_null_name(WASMModuleCommon *module_common, char *error_buf,
}
WASMModuleCommon *
wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf,
uint32 error_buf_size)
wasm_runtime_load_ex(uint8 *buf, uint32 size, const LoadArgs *args,
char *error_buf, uint32 error_buf_size)
{
WASMModuleCommon *module_common = NULL;
if (!args) {
return NULL;
}
if (get_package_type(buf, size) == Wasm_Module_Bytecode) {
#if WASM_ENABLE_INTERP != 0
module_common =
@ -1345,13 +1349,13 @@ wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf,
#if WASM_ENABLE_MULTI_MODULE != 0
true,
#endif
error_buf, error_buf_size);
args, error_buf, error_buf_size);
#endif
}
else if (get_package_type(buf, size) == Wasm_Module_AoT) {
#if WASM_ENABLE_AOT != 0
module_common = (WASMModuleCommon *)aot_load_from_aot_file(
buf, size, error_buf, error_buf_size);
buf, size, args, error_buf, error_buf_size);
#endif
}
else {
@ -1367,10 +1371,21 @@ wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf,
LOG_DEBUG("WASM module load failed");
return NULL;
}
/*TODO: use file name as name and register with name? */
return register_module_with_null_name(module_common, error_buf,
error_buf_size);
}
WASMModuleCommon *
wasm_runtime_load(uint8 *buf, uint32 size, char *error_buf,
uint32 error_buf_size)
{
LoadArgs args = { 0 };
args.name = "";
return wasm_runtime_load_ex(buf, size, &args, error_buf, error_buf_size);
}
WASMModuleCommon *
wasm_runtime_load_from_sections(WASMSection *section_list, bool is_aot,
char *error_buf, uint32 error_buf_size)
@ -6501,6 +6516,7 @@ wasm_runtime_load_depended_module(const WASMModuleCommon *parent_module,
bool ret = false;
uint8 *buffer = NULL;
uint32 buffer_size = 0;
LoadArgs args = { 0 };
/* check the registered module list of the parent */
sub_module = wasm_runtime_search_sub_module(parent_module, sub_module_name);
@ -6547,16 +6563,18 @@ wasm_runtime_load_depended_module(const WASMModuleCommon *parent_module,
LOG_DEBUG("moudle %s type error", sub_module_name);
goto destroy_file_buffer;
}
args.name = (char *)sub_module_name;
if (get_package_type(buffer, buffer_size) == Wasm_Module_Bytecode) {
#if WASM_ENABLE_INTERP != 0
sub_module = (WASMModuleCommon *)wasm_load(buffer, buffer_size, false,
error_buf, error_buf_size);
sub_module = (WASMModuleCommon *)wasm_load(
buffer, buffer_size, false, &args, error_buf, error_buf_size);
#endif
}
else if (get_package_type(buffer, buffer_size) == Wasm_Module_AoT) {
#if WASM_ENABLE_AOT != 0
sub_module = (WASMModuleCommon *)aot_load_from_aot_file(
buffer, buffer_size, error_buf, error_buf_size);
buffer, buffer_size, &args, error_buf, error_buf_size);
#endif
}
if (!sub_module) {

View File

@ -85,7 +85,7 @@ aot_add_llvm_func1(const AOTCompContext *comp_ctx, LLVMModuleRef module,
uint32 func_index, uint32 param_count, LLVMTypeRef func_type,
const char *prefix)
{
char func_name[48];
char func_name[48] = { 0 };
LLVMValueRef func;
LLVMValueRef local_value;
uint32 i, j;

View File

@ -517,10 +517,21 @@ struct WASMModuleCommon;
typedef struct WASMModuleCommon *wasm_module_t;
#endif
#ifndef LOAD_ARGS_OPTION_DEFINED
#define LOAD_ARGS_OPTION_DEFINED
typedef struct LoadArgs {
char *name;
/* TODO: more fields? */
} LoadArgs;
#endif /* LOAD_ARGS_OPTION_DEFINED */
WASM_API_EXTERN own wasm_module_t* wasm_module_new(
wasm_store_t*, const wasm_byte_vec_t* binary);
// please refer to wasm_runtime_load_ex(...) in core/iwasm/include/wasm_export.h
WASM_API_EXTERN own wasm_module_t* wasm_module_new_ex(
wasm_store_t*, const wasm_byte_vec_t* binary, const LoadArgs *args);
WASM_API_EXTERN void wasm_module_delete(own wasm_module_t*);
WASM_API_EXTERN bool wasm_module_validate(wasm_store_t*, const wasm_byte_vec_t* binary);

View File

@ -183,6 +183,14 @@ typedef struct RuntimeInitArgs {
bool enable_linux_perf;
} RuntimeInitArgs;
#ifndef LOAD_ARGS_OPTION_DEFINED
#define LOAD_ARGS_OPTION_DEFINED
typedef struct LoadArgs {
char *name;
/* TODO: more fields? */
} LoadArgs;
#endif /* LOAD_ARGS_OPTION_DEFINED */
#ifndef INSTANTIATION_ARGS_OPTION_DEFINED
#define INSTANTIATION_ARGS_OPTION_DEFINED
/* WASM module instantiation arguments */
@ -419,6 +427,13 @@ WASM_RUNTIME_API_EXTERN wasm_module_t
wasm_runtime_load(uint8_t *buf, uint32_t size,
char *error_buf, uint32_t error_buf_size);
/**
* Load a WASM module with specified load argument.
*/
WASM_RUNTIME_API_EXTERN wasm_module_t
wasm_runtime_load_ex(uint8_t *buf, uint32_t size, const LoadArgs *args,
char *error_buf, uint32_t error_buf_size);
/**
* Load a WASM module from a specified WASM or AOT section list.
*

View File

@ -6043,7 +6043,7 @@ load_from_sections(WASMModule *module, WASMSection *sections,
}
static WASMModule *
create_module(char *error_buf, uint32 error_buf_size)
create_module(char *name, char *error_buf, uint32 error_buf_size)
{
WASMModule *module =
loader_malloc(sizeof(WASMModule), error_buf, error_buf_size);
@ -6058,7 +6058,7 @@ create_module(char *error_buf, uint32 error_buf_size)
/* Set start_function to -1, means no start function */
module->start_function = (uint32)-1;
module->name = "";
module->name = name;
#if WASM_ENABLE_FAST_INTERP == 0
module->br_table_cache_list = &module->br_table_cache_list_head;
@ -6138,7 +6138,7 @@ WASMModule *
wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf,
uint32 error_buf_size)
{
WASMModule *module = create_module(error_buf, error_buf_size);
WASMModule *module = create_module("", error_buf, error_buf_size);
if (!module)
return NULL;
@ -6479,9 +6479,9 @@ wasm_loader_load(uint8 *buf, uint32 size,
#if WASM_ENABLE_MULTI_MODULE != 0
bool main_module,
#endif
char *error_buf, uint32 error_buf_size)
const LoadArgs *args, char *error_buf, uint32 error_buf_size)
{
WASMModule *module = create_module(error_buf, error_buf_size);
WASMModule *module = create_module(args->name, error_buf, error_buf_size);
if (!module) {
return NULL;
}

View File

@ -28,7 +28,7 @@ wasm_loader_load(uint8 *buf, uint32 size,
#if WASM_ENABLE_MULTI_MODULE != 0
bool main_module,
#endif
char *error_buf, uint32 error_buf_size);
const LoadArgs *args, char *error_buf, uint32 error_buf_size);
/**
* Load a WASM module from a specified WASM section list.

View File

@ -2994,7 +2994,7 @@ load_from_sections(WASMModule *module, WASMSection *sections,
}
static WASMModule *
create_module(char *error_buf, uint32 error_buf_size)
create_module(char *name, char *error_buf, uint32 error_buf_size)
{
WASMModule *module =
loader_malloc(sizeof(WASMModule), error_buf, error_buf_size);
@ -3009,7 +3009,7 @@ create_module(char *error_buf, uint32 error_buf_size)
/* Set start_function to -1, means no start function */
module->start_function = (uint32)-1;
module->name = "";
module->name = name;
#if WASM_ENABLE_FAST_INTERP == 0
module->br_table_cache_list = &module->br_table_cache_list_head;
@ -3035,7 +3035,7 @@ WASMModule *
wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf,
uint32 error_buf_size)
{
WASMModule *module = create_module(error_buf, error_buf_size);
WASMModule *module = create_module("", error_buf, error_buf_size);
if (!module)
return NULL;
@ -3206,10 +3206,10 @@ load(const uint8 *buf, uint32 size, WASMModule *module, char *error_buf,
}
WASMModule *
wasm_loader_load(uint8 *buf, uint32 size, char *error_buf,
wasm_loader_load(uint8 *buf, uint32 size, const LoadArgs *args, char *error_buf,
uint32 error_buf_size)
{
WASMModule *module = create_module(error_buf, error_buf_size);
WASMModule *module = create_module(args->name, error_buf, error_buf_size);
if (!module) {
return NULL;
}

View File

@ -60,13 +60,13 @@ wasm_load(uint8 *buf, uint32 size,
#if WASM_ENABLE_MULTI_MODULE != 0
bool main_module,
#endif
char *error_buf, uint32 error_buf_size)
const LoadArgs *name, char *error_buf, uint32 error_buf_size)
{
return wasm_loader_load(buf, size,
#if WASM_ENABLE_MULTI_MODULE != 0
main_module,
#endif
error_buf, error_buf_size);
name, error_buf, error_buf_size);
}
WASMModule *

View File

@ -508,7 +508,7 @@ wasm_load(uint8 *buf, uint32 size,
#if WASM_ENABLE_MULTI_MODULE != 0
bool main_module,
#endif
char *error_buf, uint32 error_buf_size);
const LoadArgs *args, char *error_buf, uint32 error_buf_size);
WASMModule *
wasm_load_from_sections(WASMSection *section_list, char *error_buf,

View File

@ -0,0 +1,63 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required (VERSION 3.14)
project(linux_perf_sample)
if(NOT CMAKE_HOST_LINUX)
message(FATAL_ERROR "This sample only works on linux")
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package(WASISDK REQUIRED)
################ runtime settings ################
string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
include(CheckPIESupported)
# AOT and JIT byd default
set(WAMR_BUILD_AOT 1)
set(WAMR_BUILD_INTERP 0)
set(WAMR_BUILD_JIT 1)
# wasm32-wasi
set(WAMR_BUILD_LIBC_BUILTIN 0)
set(WAMR_BUILD_LIBC_WASI 1)
# mvp
set(WAMR_BUILD_BULK_MEMORY 1)
set(WAMR_BUILD_REF_TYPES 1)
set(WAMR_BUILD_SIMD 1)
set(WAMR_BUILD_TAIL_CALL 1)
# trap information
set(WAMR_BUILD_DUMP_CALL_STACK 1)
# linux perf
set(WAMR_BUILD_LINUX_PERF 1)
#
#set(WAMR_BUILD_THREAD_MGR 0)
# vmlib
set(WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
include(${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
add_library(vmlib SHARED ${WAMR_RUNTIME_LIB_SOURCE})
target_include_directories(vmlib INTERFACE ${WAMR_ROOT_DIR}/core/iwasm/include)
target_link_libraries (vmlib ${LLVM_AVAILABLE_LIBS} -lm -ldl)
################ host ################
add_executable(${PROJECT_NAME} host/demo.c)
target_link_libraries(${PROJECT_NAME} vmlib)
################ aot + wasm ################
include(ExternalProject)
ExternalProject_Add(wasm
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wasm"
CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/wasm -B build
-DCMAKE_TOOLCHAIN_FILE=${WASISDK_TOOLCHAIN}
BUILD_COMMAND ${CMAKE_COMMAND} --build build
INSTALL_COMMAND ${CMAKE_COMMAND} --install build --prefix ${CMAKE_CURRENT_BINARY_DIR}
)

View File

@ -0,0 +1,90 @@
# linux perf sample introduction
This is a sample to show how to use the Linux perf tool to profile the execution of a WebAssembly application. And how to use the [Flamegraph](https://www.brendangregg.com/flamegraphs.html) tool to visualize the profiling result.
## Build and run the sample
There are two Wasm modules and their instance will be created and run in the sample. [The first module](./wasm/fib.c) is a simple Wasm module that calculates the Fibonacci number. [The second module](./wasm/ackermann.c) is a simple Wasm module that execute the Ackermann function. The target is enable to profile the execution of both two modules separately.
```bash
$ cmake -S . -B build
$ cmake --build build
```
### Profile the execution
```bash
$ cd build
$ perf record -k mono -g --output=perf.data -- ./linux_perf_sample
```
Enable to use `perf report --stdio` to do a quick analysis of the profiling result.
### Visualize the profiling result
Need to download Flamegraph tool from [Flamegraph](https://github.com/brendangregg/FlameGraph/releases/tag/v1.0) firstly.
```bash
$ perf script > out.perf
$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
$ ./FlameGraph/flamegraph.pl out.folded > perf.svg
```
In this result, you'll see two modules's profiling result and all wasm functions are named as "aot_func#N" which is a little hard to distinguish.
![perf.png](./pics/perf.png)
### Separate profiling result
[process_folded_data.py](../../test-tools/flame-graph-helper/process_folded_data.py) is a script can a) translate "aot_func#N" into its original function name in name sections, b) separate the profiling result of different modules.
In this sample, we want to separate `fib` and `ackermann` profiling data from _out.folded_. In [demo](host/demo.c), we decide to name the module of `fib1.wasm` as `fib2` and the module of `ackermann1.wasm` as `ackermann2`.
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names fib2=./fib1.wasm,ackermann2=./ackermann1.wasm out.folded
-> write into out.fib2.translated
-> write into out.ackermann2.translated
-> write into out.translated
```
More scenarios:
if only using one wasm during profiling, the script can be used like this:
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm <wasm_file> --folded <folded_file>
```
if only using one wasm during profiling and specify the module name via APIs, the script can be used like this:
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names <module name>=<wasm_file> --folded <folded_file>
```
if only using one wasm during profiling and specify the module name, which is same with the basename of wasm file, via APIs, the script can be used like this:
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm <wasm_file> --folded <folded_file>
```
if using multiple wasm during profiling and specify module names, which are same with basename of wasm files, via APIs, the script can be used like this:
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm <wasm_file> --wasm <wasm_file> --wasm <wasm_file> --folded <folded_file>
```
if using multiple wasm during profiling and specify module names via APIs, the script can be used like this:
```bash
$ python process_folded_data.py --wabt_home /opt/wabt --wasm_names <module_name>=<wasm_file>,<module_name>=<wasm_file>,<module_name>=<wasm_file> --folded <folded_file>
```
Now we have two flame-graphs for two wasm modules:
![fib.svg](./pics/perf.fib.svg)
![ackermann.svg](./pics/perf.ackermann.svg)
## Reference
- [perf_tune](../../doc/perf_tune.md)

View File

@ -0,0 +1,14 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
include(FindPackageHandleStandardArgs)
find_file(WAMRC_BIN
NAMES wamrc
DOC "search wamrc"
HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../../../wamr-compiler/build
REQUIRED
)
find_package_handle_standard_args(WAMRC REQUIRED_VARS WAMRC_BIN)
mark_as_advanced(WAMRC_BIN)

View File

@ -0,0 +1,23 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
include(FindPackageHandleStandardArgs)
file(GLOB WASISDK_SEARCH_PATH "/opt/wasi-sdk-*")
find_path(WASISDK_HOME
NAMES share/wasi-sysroot
PATHS ${WASISDK_SEARCH_PATH}
NO_DEFAULT_PATH
REQUIRED
)
string(REGEX MATCH [0-9]+\.[0-9]+\.*[0-9]* WASISDK_VERSION ${WASISDK_HOME})
find_package_handle_standard_args(WASISDK REQUIRED_VARS WASISDK_HOME VERSION_VAR WASISDK_VERSION)
if(WASISDK_FOUND)
set(WASISDK_CC_COMMAND ${WASISDK_HOME}/bin/clang)
set(WASISDK_CXX_COMMAND ${WASISDK_HOME}/bin/clang++)
set(WASISDK_TOOLCHAIN ${WASISDK_HOME}/share/cmake/wasi-sdk.cmake)
set(WASISDK_SYSROOT ${WASISDK_HOME}/share/wasi-sysroot)
endif()

View File

@ -0,0 +1,198 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <assert.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wasm_c_api.h"
#define own
/* return a copy of the file stem of a file path */
static own char *
stem(const char *file_path)
{
char *base_name = basename(file_path);
char *s = strdup(base_name);
char *dot = strchr(s, '.');
assert(dot);
*dot = '\0';
return s;
}
static void
guest_i32_to_wasm_i32_array(int *args, unsigned argc, wasm_val_t *data,
unsigned datac)
{
for (unsigned i = 0; i < argc && i < datac; i++) {
memset(&data[i], 0, sizeof(wasm_val_t));
data[i].kind = WASM_I32;
data[i].of.i32 = args[i];
}
}
int
load_run_wasm_file(wasm_engine_t *engine, const char *file_path, int *args,
unsigned argc)
{
wasm_store_t *store = wasm_store_new(engine);
// Load binary.
printf("Loading binary...\n");
FILE *file = fopen(file_path, "rb");
assert(file);
int ret = fseek(file, 0L, SEEK_END);
assert(ret == 0);
long file_size = ftell(file);
assert(file_size != -1);
ret = fseek(file, 0L, SEEK_SET);
assert(ret == 0);
wasm_byte_vec_t binary = { 0 };
wasm_byte_vec_new_uninitialized(&binary, file_size);
size_t nread = fread(binary.data, file_size, 1, file);
fclose(file);
// Compile.
printf("Compiling module...\n");
// Use its file name as the module name
char *file_name = stem(file_path);
assert(file_name);
LoadArgs load_args = { 0 };
load_args.name = file_name;
own wasm_module_t *module = wasm_module_new_ex(store, &binary, &load_args);
wasm_byte_vec_delete(&binary);
assert(module);
// Use export type to find the function index to call later
wasm_exporttype_vec_t export_types = { 0 };
wasm_module_exports(module, &export_types);
int func_to_call = -1;
for (unsigned i = 0; i < export_types.num_elems; i++) {
const wasm_name_t *name = wasm_exporttype_name(export_types.data[i]);
if (strncmp(name->data, "run", 3) == 0) {
func_to_call = i;
break;
}
}
assert(func_to_call != -1);
// Instantiate.
printf("Instantiating module...\n");
wasm_extern_vec_t imports = WASM_EMPTY_VEC;
own wasm_instance_t *instance = wasm_instance_new_with_args(
store, module, &imports, NULL, 16 * 1024 * 1024, 1 * 1024 * 1024);
assert(instance);
// Extract export.
printf("Extracting export...\n");
own wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
assert(exports.size);
assert(wasm_extern_kind(exports.data[func_to_call]) == WASM_EXTERN_FUNC);
const wasm_func_t *run_func =
wasm_extern_as_func(exports.data[func_to_call]);
assert(run_func);
wasm_module_delete(module);
wasm_instance_delete(instance);
// Call.
printf("Calling export...\n");
wasm_val_t as[4] = { 0 };
guest_i32_to_wasm_i32_array(args, argc, as, 4);
wasm_val_vec_t params = WASM_ARRAY_VEC(as);
wasm_val_t rs[1] = { WASM_I32_VAL(0) };
wasm_val_vec_t results = WASM_ARRAY_VEC(rs);
wasm_trap_t *trap = wasm_func_call(run_func, &params, &results);
assert(!trap);
wasm_extern_vec_delete(&exports);
free(file_name);
wasm_store_delete(store);
{
nread = nread;
ret = ret;
trap = trap;
}
return 0;
}
void *
load_run_fib_wasm(void *arg)
{
wasm_engine_t *engine = (wasm_engine_t *)arg;
int args[] = { 40 };
load_run_wasm_file(engine, "./fib1.wasm", args, 1);
return NULL;
}
void *
load_run_fib_aot(void *arg)
{
wasm_engine_t *engine = (wasm_engine_t *)arg;
int args[] = { 40 };
load_run_wasm_file(engine, "./fib2.aot", args, 1);
return NULL;
}
void *
load_run_ackermann_wasm(void *arg)
{
wasm_engine_t *engine = (wasm_engine_t *)arg;
int args[] = { 3, 12 };
load_run_wasm_file(engine, "./ackermann1.wasm", args, 2);
return NULL;
}
void *
load_run_ackermann_aot(void *arg)
{
wasm_engine_t *engine = (wasm_engine_t *)arg;
int args[] = { 3, 12 };
load_run_wasm_file(engine, "./ackermann2.aot", args, 2);
return NULL;
}
int
main(int argc, const char *argv[])
{
// Initialize.
printf("Initializing...\n");
wasm_config_t *config = wasm_config_new();
wasm_config_set_linux_perf_opt(config, true);
wasm_engine_t *engine = wasm_engine_new_with_config(config);
pthread_t tid[4] = { 0 };
/* FIXME: uncomment when it is able to run two modules with llvm-jit */
// pthread_create(&tid[0], NULL, load_run_fib_wasm, (void *)engine);
// pthread_create(&tid[2], NULL, load_run_ackermann_wasm, (void *)engine);
pthread_create(&tid[1], NULL, load_run_fib_aot, (void *)engine);
pthread_create(&tid[3], NULL, load_run_ackermann_aot, (void *)engine);
for (unsigned i = 0; i < sizeof(tid) / sizeof(tid[0]); i++)
pthread_join(tid[i], NULL);
// Shut down.
printf("Shutting down...\n");
wasm_engine_delete(engine);
// All done.
printf("Done.\n");
return 0;
}

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,605 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" width="1200" height="758" onload="init(evt)" viewBox="0 0 1200 758" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. -->
<!-- NOTES: -->
<defs>
<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
<stop stop-color="#eeeeee" offset="5%" />
<stop stop-color="#eeeeb0" offset="95%" />
</linearGradient>
</defs>
<style type="text/css">
text { font-family:Verdana; font-size:12px; fill:rgb(0,0,0); }
#search, #ignorecase { opacity:0.1; cursor:pointer; }
#search:hover, #search.show, #ignorecase:hover, #ignorecase.show { opacity:1; }
#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }
#title { text-anchor:middle; font-size:17px}
#unzoom { cursor:pointer; }
#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
.hide { display:none; }
.parent { opacity:0.5; }
</style>
<script type="text/ecmascript">
<![CDATA[
"use strict";
var details, searchbtn, unzoombtn, matchedtxt, svg, searching, currentSearchTerm, ignorecase, ignorecaseBtn;
function init(evt) {
details = document.getElementById("details").firstChild;
searchbtn = document.getElementById("search");
ignorecaseBtn = document.getElementById("ignorecase");
unzoombtn = document.getElementById("unzoom");
matchedtxt = document.getElementById("matched");
svg = document.getElementsByTagName("svg")[0];
searching = 0;
currentSearchTerm = null;
// use GET parameters to restore a flamegraphs state.
var params = get_params();
if (params.x && params.y)
zoom(find_group(document.querySelector('[x="' + params.x + '"][y="' + params.y + '"]')));
if (params.s) search(params.s);
}
// event listeners
window.addEventListener("click", function(e) {
var target = find_group(e.target);
if (target) {
if (target.nodeName == "a") {
if (e.ctrlKey === false) return;
e.preventDefault();
}
if (target.classList.contains("parent")) unzoom(true);
zoom(target);
if (!document.querySelector('.parent')) {
// we have basically done a clearzoom so clear the url
var params = get_params();
if (params.x) delete params.x;
if (params.y) delete params.y;
history.replaceState(null, null, parse_params(params));
unzoombtn.classList.add("hide");
return;
}
// set parameters for zoom state
var el = target.querySelector("rect");
if (el && el.attributes && el.attributes.y && el.attributes._orig_x) {
var params = get_params()
params.x = el.attributes._orig_x.value;
params.y = el.attributes.y.value;
history.replaceState(null, null, parse_params(params));
}
}
else if (e.target.id == "unzoom") clearzoom();
else if (e.target.id == "search") search_prompt();
else if (e.target.id == "ignorecase") toggle_ignorecase();
}, false)
// mouse-over for info
// show
window.addEventListener("mouseover", function(e) {
var target = find_group(e.target);
if (target) details.nodeValue = "Function: " + g_to_text(target);
}, false)
// clear
window.addEventListener("mouseout", function(e) {
var target = find_group(e.target);
if (target) details.nodeValue = ' ';
}, false)
// ctrl-F for search
// ctrl-I to toggle case-sensitive search
window.addEventListener("keydown",function (e) {
if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
e.preventDefault();
search_prompt();
}
else if (e.ctrlKey && e.keyCode === 73) {
e.preventDefault();
toggle_ignorecase();
}
}, false)
// functions
function get_params() {
var params = {};
var paramsarr = window.location.search.substr(1).split('&');
for (var i = 0; i < paramsarr.length; ++i) {
var tmp = paramsarr[i].split("=");
if (!tmp[0] || !tmp[1]) continue;
params[tmp[0]] = decodeURIComponent(tmp[1]);
}
return params;
}
function parse_params(params) {
var uri = "?";
for (var key in params) {
uri += key + '=' + encodeURIComponent(params[key]) + '&';
}
if (uri.slice(-1) == "&")
uri = uri.substring(0, uri.length - 1);
if (uri == '?')
uri = window.location.href.split('?')[0];
return uri;
}
function find_child(node, selector) {
var children = node.querySelectorAll(selector);
if (children.length) return children[0];
}
function find_group(node) {
var parent = node.parentElement;
if (!parent) return;
if (parent.id == "frames") return node;
return find_group(parent);
}
function orig_save(e, attr, val) {
if (e.attributes["_orig_" + attr] != undefined) return;
if (e.attributes[attr] == undefined) return;
if (val == undefined) val = e.attributes[attr].value;
e.setAttribute("_orig_" + attr, val);
}
function orig_load(e, attr) {
if (e.attributes["_orig_"+attr] == undefined) return;
e.attributes[attr].value = e.attributes["_orig_" + attr].value;
e.removeAttribute("_orig_"+attr);
}
function g_to_text(e) {
var text = find_child(e, "title").firstChild.nodeValue;
return (text)
}
function g_to_func(e) {
var func = g_to_text(e);
// if there's any manipulation we want to do to the function
// name before it's searched, do it here before returning.
return (func);
}
function update_text(e) {
var r = find_child(e, "rect");
var t = find_child(e, "text");
var w = parseFloat(r.attributes.width.value) -3;
var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
t.attributes.x.value = parseFloat(r.attributes.x.value) + 3;
// Smaller than this size won't fit anything
if (w < 2 * 12 * 0.59) {
t.textContent = "";
return;
}
t.textContent = txt;
var sl = t.getSubStringLength(0, txt.length);
// check if only whitespace or if we can fit the entire string into width w
if (/^ *$/.test(txt) || sl < w)
return;
// this isn't perfect, but gives a good starting point
// and avoids calling getSubStringLength too often
var start = Math.floor((w/sl) * txt.length);
for (var x = start; x > 0; x = x-2) {
if (t.getSubStringLength(0, x + 2) <= w) {
t.textContent = txt.substring(0, x) + "..";
return;
}
}
t.textContent = "";
}
// zoom
function zoom_reset(e) {
if (e.attributes != undefined) {
orig_load(e, "x");
orig_load(e, "width");
}
if (e.childNodes == undefined) return;
for (var i = 0, c = e.childNodes; i < c.length; i++) {
zoom_reset(c[i]);
}
}
function zoom_child(e, x, ratio) {
if (e.attributes != undefined) {
if (e.attributes.x != undefined) {
orig_save(e, "x");
e.attributes.x.value = (parseFloat(e.attributes.x.value) - x - 10) * ratio + 10;
if (e.tagName == "text")
e.attributes.x.value = find_child(e.parentNode, "rect[x]").attributes.x.value + 3;
}
if (e.attributes.width != undefined) {
orig_save(e, "width");
e.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;
}
}
if (e.childNodes == undefined) return;
for (var i = 0, c = e.childNodes; i < c.length; i++) {
zoom_child(c[i], x - 10, ratio);
}
}
function zoom_parent(e) {
if (e.attributes) {
if (e.attributes.x != undefined) {
orig_save(e, "x");
e.attributes.x.value = 10;
}
if (e.attributes.width != undefined) {
orig_save(e, "width");
e.attributes.width.value = parseInt(svg.width.baseVal.value) - (10 * 2);
}
}
if (e.childNodes == undefined) return;
for (var i = 0, c = e.childNodes; i < c.length; i++) {
zoom_parent(c[i]);
}
}
function zoom(node) {
var attr = find_child(node, "rect").attributes;
var width = parseFloat(attr.width.value);
var xmin = parseFloat(attr.x.value);
var xmax = parseFloat(xmin + width);
var ymin = parseFloat(attr.y.value);
var ratio = (svg.width.baseVal.value - 2 * 10) / width;
// XXX: Workaround for JavaScript float issues (fix me)
var fudge = 0.0001;
unzoombtn.classList.remove("hide");
var el = document.getElementById("frames").children;
for (var i = 0; i < el.length; i++) {
var e = el[i];
var a = find_child(e, "rect").attributes;
var ex = parseFloat(a.x.value);
var ew = parseFloat(a.width.value);
var upstack;
// Is it an ancestor
if (0 == 0) {
upstack = parseFloat(a.y.value) > ymin;
} else {
upstack = parseFloat(a.y.value) < ymin;
}
if (upstack) {
// Direct ancestor
if (ex <= xmin && (ex+ew+fudge) >= xmax) {
e.classList.add("parent");
zoom_parent(e);
update_text(e);
}
// not in current path
else
e.classList.add("hide");
}
// Children maybe
else {
// no common path
if (ex < xmin || ex + fudge >= xmax) {
e.classList.add("hide");
}
else {
zoom_child(e, xmin, ratio);
update_text(e);
}
}
}
search();
}
function unzoom(dont_update_text) {
unzoombtn.classList.add("hide");
var el = document.getElementById("frames").children;
for(var i = 0; i < el.length; i++) {
el[i].classList.remove("parent");
el[i].classList.remove("hide");
zoom_reset(el[i]);
if(!dont_update_text) update_text(el[i]);
}
search();
}
function clearzoom() {
unzoom();
// remove zoom state
var params = get_params();
if (params.x) delete params.x;
if (params.y) delete params.y;
history.replaceState(null, null, parse_params(params));
}
// search
function toggle_ignorecase() {
ignorecase = !ignorecase;
if (ignorecase) {
ignorecaseBtn.classList.add("show");
} else {
ignorecaseBtn.classList.remove("show");
}
reset_search();
search();
}
function reset_search() {
var el = document.querySelectorAll("#frames rect");
for (var i = 0; i < el.length; i++) {
orig_load(el[i], "fill")
}
var params = get_params();
delete params.s;
history.replaceState(null, null, parse_params(params));
}
function search_prompt() {
if (!searching) {
var term = prompt("Enter a search term (regexp " +
"allowed, eg: ^ext4_)"
+ (ignorecase ? ", ignoring case" : "")
+ "\nPress Ctrl-i to toggle case sensitivity", "");
if (term != null) search(term);
} else {
reset_search();
searching = 0;
currentSearchTerm = null;
searchbtn.classList.remove("show");
searchbtn.firstChild.nodeValue = "Search"
matchedtxt.classList.add("hide");
matchedtxt.firstChild.nodeValue = ""
}
}
function search(term) {
if (term) currentSearchTerm = term;
var re = new RegExp(currentSearchTerm, ignorecase ? 'i' : '');
var el = document.getElementById("frames").children;
var matches = new Object();
var maxwidth = 0;
for (var i = 0; i < el.length; i++) {
var e = el[i];
var func = g_to_func(e);
var rect = find_child(e, "rect");
if (func == null || rect == null)
continue;
// Save max width. Only works as we have a root frame
var w = parseFloat(rect.attributes.width.value);
if (w > maxwidth)
maxwidth = w;
if (func.match(re)) {
// highlight
var x = parseFloat(rect.attributes.x.value);
orig_save(rect, "fill");
rect.attributes.fill.value = "rgb(230,0,230)";
// remember matches
if (matches[x] == undefined) {
matches[x] = w;
} else {
if (w > matches[x]) {
// overwrite with parent
matches[x] = w;
}
}
searching = 1;
}
}
if (!searching)
return;
var params = get_params();
params.s = currentSearchTerm;
history.replaceState(null, null, parse_params(params));
searchbtn.classList.add("show");
searchbtn.firstChild.nodeValue = "Reset Search";
// calculate percent matched, excluding vertical overlap
var count = 0;
var lastx = -1;
var lastw = 0;
var keys = Array();
for (k in matches) {
if (matches.hasOwnProperty(k))
keys.push(k);
}
// sort the matched frames by their x location
// ascending, then width descending
keys.sort(function(a, b){
return a - b;
});
// Step through frames saving only the biggest bottom-up frames
// thanks to the sort order. This relies on the tree property
// where children are always smaller than their parents.
var fudge = 0.0001; // JavaScript floating point
for (var k in keys) {
var x = parseFloat(keys[k]);
var w = matches[keys[k]];
if (x >= lastx + lastw - fudge) {
count += w;
lastx = x;
lastw = w;
}
}
// display matched percent
matchedtxt.classList.remove("hide");
var pct = 100 * count / maxwidth;
if (pct != 100) pct = pct.toFixed(1)
matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
}
]]>
</script>
<rect x="0.0" y="0" width="1200.0" height="758.0" fill="url(#background)" />
<text id="title" x="600.00" y="24" >Flame Graph</text>
<text id="details" x="10.00" y="741" > </text>
<text id="unzoom" x="10.00" y="24" class="hide">Reset Zoom</text>
<text id="search" x="1090.00" y="24" >Search</text>
<text id="ignorecase" x="1174.00" y="24" >ic</text>
<text id="matched" x="1090.00" y="741" > </text>
<g id="frames">
<g >
<title>[Wasm] [fib2] fibonacci (1,321,095,222 samples, 93.80%)</title><rect x="83.2" y="341" width="1106.8" height="15.0" fill="rgb(218,159,32)" rx="2" ry="2" />
<text x="86.21" y="351.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (382,407,564 samples, 27.15%)</title><rect x="869.6" y="213" width="320.4" height="15.0" fill="rgb(245,35,33)" rx="2" ry="2" />
<text x="872.59" y="223.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (15,340,273 samples, 1.09%)</title><rect x="1177.1" y="101" width="12.9" height="15.0" fill="rgb(213,210,13)" rx="2" ry="2" />
<text x="1180.11" y="111.5" ></text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,359,552,763 samples, 96.53%)</title><rect x="51.0" y="357" width="1139.0" height="15.0" fill="rgb(252,138,7)" rx="2" ry="2" />
<text x="53.99" y="367.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="517" width="1180.0" height="15.0" fill="rgb(218,120,5)" rx="2" ry="2" />
<text x="13.00" y="527.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (27,274,310 samples, 1.94%)</title><rect x="1167.1" y="117" width="22.9" height="15.0" fill="rgb(234,117,51)" rx="2" ry="2" />
<text x="1170.11" y="127.5" >[..</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (62,450,767 samples, 4.43%)</title><rect x="1137.6" y="149" width="52.4" height="15.0" fill="rgb(225,21,1)" rx="2" ry="2" />
<text x="1140.64" y="159.5" >[Wasm..</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="485" width="1180.0" height="15.0" fill="rgb(233,228,10)" rx="2" ry="2" />
<text x="13.00" y="495.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,388,674,508 samples, 98.59%)</title><rect x="26.6" y="389" width="1163.4" height="15.0" fill="rgb(253,174,18)" rx="2" ry="2" />
<text x="29.59" y="399.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,170,751,868 samples, 83.12%)</title><rect x="209.2" y="309" width="980.8" height="15.0" fill="rgb(227,120,21)" rx="2" ry="2" />
<text x="212.17" y="319.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="533" width="1180.0" height="15.0" fill="rgb(254,106,47)" rx="2" ry="2" />
<text x="13.00" y="543.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (120,820,158 samples, 8.58%)</title><rect x="1088.7" y="165" width="101.3" height="15.0" fill="rgb(205,47,33)" rx="2" ry="2" />
<text x="1091.74" y="175.5" >[Wasm] [fib2..</text>
</g>
<g >
<title>invoke_i_i (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="565" width="1180.0" height="15.0" fill="rgb(238,144,29)" rx="2" ry="2" />
<text x="13.00" y="575.5" >invoke_i_i</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,375,872,224 samples, 97.68%)</title><rect x="37.3" y="373" width="1152.7" height="15.0" fill="rgb(219,165,53)" rx="2" ry="2" />
<text x="40.32" y="383.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>load_run_wasm_file (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="645" width="1180.0" height="15.0" fill="rgb(233,129,29)" rx="2" ry="2" />
<text x="13.00" y="655.5" >load_run_wasm_file</text>
</g>
<g >
<title>load_run_fib_aot (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="661" width="1180.0" height="15.0" fill="rgb(248,119,41)" rx="2" ry="2" />
<text x="13.00" y="671.5" >load_run_fib_aot</text>
</g>
<g >
<title>[Wasm] [fib2] run (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="549" width="1180.0" height="15.0" fill="rgb(235,16,45)" rx="2" ry="2" />
<text x="13.00" y="559.5" >[Wasm] [fib2] run</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (42,420,273 samples, 3.01%)</title><rect x="1154.4" y="133" width="35.6" height="15.0" fill="rgb(239,213,43)" rx="2" ry="2" />
<text x="1157.42" y="143.5" >[Wa..</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,266,323,684 samples, 89.91%)</title><rect x="129.1" y="325" width="1060.9" height="15.0" fill="rgb(248,225,3)" rx="2" ry="2" />
<text x="132.10" y="335.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>linux_perf_samp (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="693" width="1180.0" height="15.0" fill="rgb(241,6,8)" rx="2" ry="2" />
<text x="13.00" y="703.5" >linux_perf_samp</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (280,259,464 samples, 19.90%)</title><rect x="955.2" y="197" width="234.8" height="15.0" fill="rgb(243,108,14)" rx="2" ry="2" />
<text x="958.17" y="207.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>start_thread (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="677" width="1180.0" height="15.0" fill="rgb(248,153,5)" rx="2" ry="2" />
<text x="13.00" y="687.5" >start_thread</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (2,334,521 samples, 0.17%)</title><rect x="1188.0" y="69" width="1.9" height="15.0" fill="rgb(237,160,49)" rx="2" ry="2" />
<text x="1190.97" y="79.5" ></text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (666,394,609 samples, 47.31%)</title><rect x="631.7" y="245" width="558.3" height="15.0" fill="rgb(213,187,32)" rx="2" ry="2" />
<text x="634.67" y="255.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (943,121,736 samples, 66.96%)</title><rect x="399.9" y="277" width="790.1" height="15.0" fill="rgb(219,155,33)" rx="2" ry="2" />
<text x="402.87" y="287.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,169,581 samples, 0.08%)</title><rect x="1188.9" y="53" width="1.0" height="15.0" fill="rgb(237,124,2)" rx="2" ry="2" />
<text x="1191.95" y="63.5" ></text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,169,581 samples, 0.08%)</title><rect x="1188.9" y="37" width="1.0" height="15.0" fill="rgb(251,93,20)" rx="2" ry="2" />
<text x="1191.95" y="47.5" ></text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (194,755,877 samples, 13.83%)</title><rect x="1026.8" y="181" width="163.2" height="15.0" fill="rgb(215,30,29)" rx="2" ry="2" />
<text x="1029.80" y="191.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,406,148,966 samples, 99.83%)</title><rect x="12.0" y="437" width="1178.0" height="15.0" fill="rgb(254,68,36)" rx="2" ry="2" />
<text x="14.95" y="447.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>all (1,408,481,525 samples, 100%)</title><rect x="10.0" y="709" width="1180.0" height="15.0" fill="rgb(236,29,9)" rx="2" ry="2" />
<text x="13.00" y="719.5" ></text>
</g>
<g >
<title>wasm_func_call (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="629" width="1180.0" height="15.0" fill="rgb(245,143,47)" rx="2" ry="2" />
<text x="13.00" y="639.5" >wasm_func_call</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (5,943,602 samples, 0.42%)</title><rect x="1185.0" y="85" width="5.0" height="15.0" fill="rgb(251,87,10)" rx="2" ry="2" />
<text x="1187.98" y="95.5" ></text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="469" width="1180.0" height="15.0" fill="rgb(249,99,1)" rx="2" ry="2" />
<text x="13.00" y="479.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>wasm_runtime_call_wasm (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="613" width="1180.0" height="15.0" fill="rgb(241,228,3)" rx="2" ry="2" />
<text x="13.00" y="623.5" >wasm_runtime_call_wasm</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,401,486,191 samples, 99.50%)</title><rect x="15.9" y="405" width="1174.1" height="15.0" fill="rgb(242,25,16)" rx="2" ry="2" />
<text x="18.86" y="415.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>aot_call_function (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="597" width="1180.0" height="15.0" fill="rgb(234,207,25)" rx="2" ry="2" />
<text x="13.00" y="607.5" >aot_call_function</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (531,941,563 samples, 37.77%)</title><rect x="744.3" y="229" width="445.7" height="15.0" fill="rgb(233,184,39)" rx="2" ry="2" />
<text x="747.31" y="239.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,406,148,966 samples, 99.83%)</title><rect x="12.0" y="453" width="1178.0" height="15.0" fill="rgb(246,125,49)" rx="2" ry="2" />
<text x="14.95" y="463.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,061,055,435 samples, 75.33%)</title><rect x="301.1" y="293" width="888.9" height="15.0" fill="rgb(248,39,32)" rx="2" ry="2" />
<text x="304.07" y="303.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="501" width="1180.0" height="15.0" fill="rgb(217,150,5)" rx="2" ry="2" />
<text x="13.00" y="511.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (1,403,816,880 samples, 99.67%)</title><rect x="13.9" y="421" width="1176.1" height="15.0" fill="rgb(208,33,41)" rx="2" ry="2" />
<text x="16.91" y="431.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>[Wasm] [fib2] fibonacci (800,646,766 samples, 56.84%)</title><rect x="519.2" y="261" width="670.8" height="15.0" fill="rgb(252,110,19)" rx="2" ry="2" />
<text x="522.19" y="271.5" >[Wasm] [fib2] fibonacci</text>
</g>
<g >
<title>invoke_native_with_hw_bound_check (1,408,481,525 samples, 100.00%)</title><rect x="10.0" y="581" width="1180.0" height="15.0" fill="rgb(243,145,41)" rx="2" ry="2" />
<text x="13.00" y="591.5" >invoke_native_with_hw_bound_check</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

BIN
samples/linux-perf/pics/perf.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,42 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required (VERSION 3.14)
project(linux_perf_sample_wasm)
include(CMakePrintHelpers)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake)
find_package(WAMRC REQUIRED)
################ wasm ################
add_executable(fib_wasm fib.c)
set_target_properties(fib_wasm PROPERTIES SUFFIX .wasm)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fib_wasm.wasm DESTINATION . RENAME fib1.wasm)
add_executable(ackermann_wasm ackermann.c)
set_target_properties(ackermann_wasm PROPERTIES SUFFIX .wasm)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ackermann_wasm.wasm DESTINATION . RENAME ackermann1.wasm)
################ aot ################
add_custom_target(fib_aot
ALL
COMMAND ${WAMRC_BIN} --enable-linux-perf -o fib2.aot fib_wasm.wasm
DEPENDS fib_wasm
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fib2.aot DESTINATION .)
add_custom_target(ackermann_aot
ALL
COMMAND ${WAMRC_BIN} --enable-linux-perf -o ackermann2.aot ackermann_wasm.wasm
DEPENDS ackermann_wasm
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ackermann2.aot DESTINATION .)

View File

@ -0,0 +1,38 @@
#include <stdio.h>
// Ackermann function
unsigned long
ackermann(unsigned long m, unsigned long n)
{
if (m == 0) {
return n + 1;
}
else if (n == 0) {
return ackermann(m - 1, 1);
}
else {
return ackermann(m - 1, ackermann(m, n - 1));
}
}
__attribute__((export_name("run"))) int
run(int m, int n)
{
int result = ackermann(m, n);
printf("ackermann(%d, %d)=%d\n", m, n, result);
return result;
}
int
main()
{
unsigned long m, n, result;
// Example usage:
m = 3;
n = 2;
result = ackermann(m, n);
printf("Ackermann(%lu, %lu) = %lu\n", m, n, result);
return 0;
}

View File

@ -0,0 +1,32 @@
#include <stdio.h>
#include <stdlib.h>
int
fibonacci(int n)
{
if (n <= 0)
return 0;
if (n == 1)
return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
__attribute__((export_name("run"))) int
run(int n)
{
int result = fibonacci(n);
printf("fibonacci(%d)=%d\n", n, result);
return result;
}
int
main(int argc, char **argv)
{
int n = atoi(argv[1]);
printf("fibonacci(%d)=%d\n", n, fibonacci(n));
return 0;
}

View File

@ -0,0 +1,2 @@
*.*
!*.py

View File

@ -0,0 +1,325 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
"""
It is used to process *out.folded* file generated by [FlameGraph](https://github.com/brendangregg/FlameGraph).
- translate jitted function names, which are in a form like `aot_func#N` or `[module name]#aot_func#N`, into corresponding names in a name section in .wasm
- divide the translated functions into different modules if the module name is specified in the symbol
Usage:
After
``` bash
# collect profiling data in perf.data
$ perf script -i perf.data > out.perf
$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
```
Use this script to translate the function names in out.folded
```
$ python translate_wasm_function_name.py --wabt_home <wabt-installation> --folded out.folded <.wasm>
# out.folded -> out.folded.translated
```
"""
import argparse
import os
from pathlib import Path
import re
import shlex
import subprocess
from typing import Dict, List
# parse arguments like "foo=bar,fiz=biz" into a dictatory {foo:bar,fiz=biz}
class ParseKVArgs(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, dict())
for value in values.split(","):
k, v = value.split("=")
getattr(namespace, self.dest)[k] = v
def calculate_import_function_count(
wasm_objdump_bin: Path, module_names: Dict[str, Path]
) -> Dict[str, int]:
"""
for every wasm file in <module_names>, calculate the number of functions in the import section.
using "<wasm_objdump_bin> -j Import -x <wasm_file>"
"""
assert wasm_objdump_bin.exists()
import_function_counts = {}
for module_name, wasm_path in module_names.items():
assert wasm_path.exists()
command = f"{wasm_objdump_bin} -j Import -x {wasm_path}"
p = subprocess.run(
shlex.split(command),
capture_output=True,
check=False,
text=True,
universal_newlines=True,
)
if p.stderr:
print("No content in import section")
import_function_counts[module_name] = 0
continue
import_function_count = 0
for line in p.stdout.split(os.linesep):
line = line.strip()
if not line:
continue
if not " func" in line:
continue
m = re.search(r"^-\s+func", line)
assert m
import_function_count += 1
# print(f"! there are {import_function_count} import function in {module_name}")
import_function_counts[module_name] = import_function_count
return import_function_counts
def collect_name_section_content(
wasm_objdump_bin: Path, module_names: Dict[str, Path]
) -> Dict[str, Dict[int, str]]:
"""
for every wasm file in <module_names>, get the content of name section.
execute "wasm_objdump_bin -j name -x wasm_file"
"""
assert wasm_objdump_bin.exists()
name_sections = {}
for module_name, wasm_path in module_names.items():
assert wasm_path.exists()
command = f"{wasm_objdump_bin} -j name -x {wasm_path}"
p = subprocess.run(
shlex.split(command),
capture_output=True,
check=False,
text=True,
universal_newlines=True,
)
if p.stderr:
print("No content in name section")
name_sections[module_name] = {}
continue
name_section = {}
for line in p.stdout.split(os.linesep):
line = line.strip()
if not line:
continue
if not " func" in line:
continue
# - func[N] <__imported_wasi_snapshot_preview1_fd_close>
m = re.match(r"- func\[(\d+)\] <(.+)>", line)
assert m
func_index, func_name = m.groups()
name_section.update({int(func_index): func_name})
name_sections[module_name] = name_section
return name_sections
def is_stack_check_mode(folded: Path) -> bool:
"""
check if there is a function name looks like "aot_func_internal#N", it means that WAMR adds a stack check function before the original function.
"""
with open(folded, "rt", encoding="utf-8") as f:
for line in f:
line = line.strip()
if "aot_func_internal" in line:
return True
return False
def replace_function_name(
import_function_counts: Dict[str, int],
name_sections: Dict[str, Dict[int, str]],
folded_in: Path,
module_names: Dict[str, Path],
) -> None:
"""
read content in <folded_in>. every line contains symbols which are separated by ";".
Usually, all jitted functions are in the form of "aot_func#N". N is its function index. Use the index to find the corresponding function name in the name section.
if there is a function name looks like "aot_func_internal#N", it means that WAMR adds a stack check function before the original function.
In this case, "aot_func#N" should be translated with "_precheck" as a suffix and "aot_func_internal#N" should be treated as the original one
"""
assert folded_in.exists(), f"{folded_in} doesn't exist"
stack_check_mode = is_stack_check_mode(folded_in)
# every wasm has a translated out.folded, like out.<module_name>.folded.translated
folded_out_files = {}
for module_name in module_names.keys():
wasm_folded_out_path = folded_in.with_suffix(f".{module_name}.translated")
print(f"-> write into {wasm_folded_out_path}")
folded_out_files[module_name] = wasm_folded_out_path.open(
"wt", encoding="utf-8"
)
# Plus a default translated out.folded
default_folded_out_path = folded_in.with_suffix(".translated")
print(f"-> write into {default_folded_out_path}")
default_folded_out = default_folded_out_path.open("wt", encoding="utf-8")
with folded_in.open("rt", encoding="utf-8") as f_in:
for line in f_in:
line = line.strip()
m = re.match(r"(.*) (\d+)", line)
assert m
syms, samples = m.groups()
new_line = []
last_function_module_name = ""
for sym in syms.split(";"):
if not "aot_func" in sym:
new_line.append(sym)
continue
# [module_name]#aot_func#N or aot_func#N
splitted = sym.split("#")
module_name = "" if splitted[0] == "aot_func" else splitted[0]
# remove [ and ]
module_name = module_name[1:-1]
if len(module_name) == 0 and len(module_names) > 1:
raise RuntimeError(
f"{sym} doesn't have a module name, but there are multiple wasm files"
)
if not module_name in module_names:
raise RuntimeError(
f"❌ can't find corresponds wasm file for {module_name}"
)
last_function_module_name = module_name
func_idx = int(splitted[-1])
# adjust index
func_idx = func_idx + import_function_counts[module_name]
# print(f"🔍 {module_name} {splitted[1]} {func_idx}")
if func_idx in name_sections[module_name]:
if len(module_name) > 0:
wasm_func_name = f"[Wasm] [{module_name}] {name_sections[module_name][func_idx]}"
else:
wasm_func_name = (
f"[Wasm] {name_sections[module_name][func_idx]}"
)
else:
if len(module_name) > 0:
wasm_func_name = f"[Wasm] [{module_name}] func[{func_idx}]"
else:
wasm_func_name = f"[Wasm] func[{func_idx}]"
if stack_check_mode:
# aot_func_internal -> xxx
# aot_func --> xxx_precheck
if "aot_func" == splitted[1]:
wasm_func_name += "_precheck"
new_line.append(wasm_func_name)
line = ";".join(new_line)
line += f" {samples}"
# always write into the default output
default_folded_out.write(line + os.linesep)
# based on the module name of last function, write into the corresponding output
if len(last_function_module_name) > 0:
folded_out_files[last_function_module_name].write(line + os.linesep)
default_folded_out.close()
for f in folded_out_files.values():
f.close()
def main(wabt_home: str, folded: str, module_names: Dict[str, Path]) -> None:
wabt_home = Path(wabt_home)
assert wabt_home.exists()
folded = Path(folded)
assert folded.exists()
wasm_objdump_bin = wabt_home.joinpath("bin", "wasm-objdump")
import_function_counts = calculate_import_function_count(
wasm_objdump_bin, module_names
)
name_sections = collect_name_section_content(wasm_objdump_bin, module_names)
replace_function_name(import_function_counts, name_sections, folded, module_names)
if __name__ == "__main__":
argparse = argparse.ArgumentParser()
argparse.add_argument(
"--wabt_home", required=True, help="wabt home, like /opt/wabt-1.0.33"
)
argparse.add_argument(
"--wasm",
action="append",
default=[],
help="wasm files for profiling before. like --wasm apple.wasm --wasm banana.wasm",
)
argparse.add_argument(
"--wasm_names",
action=ParseKVArgs,
default={},
metavar="module_name=wasm_file, ...",
help="multiple wasm files and their module names, like a=apple.wasm,b=banana.wasm,c=cake.wasm",
)
argparse.add_argument(
"folded_file",
help="a out.folded generated by flamegraph/stackcollapse-perf.pl",
)
args = argparse.parse_args()
if not args.wasm and not args.wasm_names:
print("Please specify wasm files with either --wasm or --wasm_names")
exit(1)
# - only one wasm file. And there is no [module name] in out.folded
# - multiple wasm files. via `--wasm X --wasm Y --wasm Z`. And there is [module name] in out.folded. use the basename of wasm as the module name
# - multiple wasm files. via `--wasm_names X=x,Y=y,Z=z`. And there is [module name] in out.folded. use the specified module name
module_names = {}
if args.wasm_names:
for name, wasm_path in args.wasm_names.items():
module_names[name] = Path(wasm_path)
else:
# use the basename of wasm as the module name
for wasm in args.wasm:
wasm_path = Path(wasm)
module_names[wasm_path.stem] = wasm_path
main(args.wabt_home, args.folded_file, module_names)

View File

@ -1,213 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
"""
It is used to translate jitted functions' names(in out.folded) to coorespond name in name section in .wasm
Usage:
After
```
$ perf script -i perf.data > out.perf
# fold call stacks
$ ./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
```
Add a step:
```
# translate jitted functions' names
$ python translate_wasm_function_name.py --wabt_home <wabt-installation> --folded out.folded <.wasm>
# out.folded -> out.folded.translated
$ ls out.folded.translated
```
Then
```
# generate flamegraph
$ ./FlameGraph/flamegraph.pl out.folded.translated > perf.wasm.svg
```
"""
import argparse
import os
from pathlib import Path
import re
import shlex
import subprocess
def preflight_check(wabt_home: Path) -> Path:
"""
if wasm-objdump exists in wabt_home
"""
wasm_objdump_bin = wabt_home.joinpath("bin", "wasm-objdump")
if not wasm_objdump_bin.exists():
raise RuntimeError(f"wasm-objdump not found in {wabt_home}")
return wasm_objdump_bin
def collect_import_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict:
"""
execute "wasm_objdump_bin -j Import -x <wasm_file>" and return a dict like {function: X, global: Y, memory: Z, table: N}
"""
assert wasm_objdump_bin.exists()
assert wasm_file.exists()
command = f"{wasm_objdump_bin} -j Import -x {wasm_file}"
p = subprocess.run(
shlex.split(command),
capture_output=True,
check=False,
text=True,
universal_newlines=True,
)
if p.stderr:
print("No content in import section")
return {}
import_section = {}
for line in p.stdout.split(os.linesep):
line = line.strip()
if not line:
continue
if re.search(r"^-\s+func", line):
import_section.update(function=import_section.get("function", 0) + 1)
else:
pass
assert len(import_section) > 0, "failed to retrive content of import section"
return import_section
def collect_name_section_content(wasm_objdump_bin: Path, wasm_file: Path) -> dict:
"""
execute "wasm_objdump_bin -j name -x wasm_file" and store the output in a dict
{1: xxxx, 2: yyyy, 3: zzzz}
"""
assert wasm_objdump_bin.exists()
assert wasm_file.exists()
command = f"{wasm_objdump_bin} -j name -x {wasm_file}"
p = subprocess.run(
shlex.split(command),
capture_output=True,
check=False,
text=True,
universal_newlines=True,
)
if p.stderr:
raise RuntimeError(f"not found name section in {wasm_file}")
name_section = {}
for line in p.stdout.split(os.linesep):
line = line.strip()
if not line:
continue
# - func[0] <__imported_wasi_snapshot_preview1_fd_close>
if line.startswith("- func"):
m = re.match(r"- func\[(\d+)\] <(.+)>", line)
assert m
func_index, func_name = m.groups()
name_section.update({int(func_index): func_name})
assert name_section
return name_section
def replace_function_name(
import_section: dict, name_section: dict, folded_in: str, folded_out: str
) -> None:
"""
read content in <folded_in>. each line will be like:
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
symbol names are spearated by ";"
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
"""
folded_in = Path(folded_in)
assert folded_in.exists()
folded_out = Path(folded_out)
import_function_count = import_section.get("function", 0)
with folded_in.open("rt", encoding="utf-8") as f_in, folded_out.open(
"wt", encoding="utf-8"
) as f_out:
precheck_mode = False
for line in f_in:
line = line.strip()
if "aot_func_internal" in line:
precheck_mode = True
f_in.seek(0)
for line in f_in:
new_line = []
line = line.strip()
m = re.match(r"(.*) (\d+)", line)
syms, samples = m.groups()
for sym in syms.split(";"):
m = re.match(r"aot_func(_internal)?#(\d+)", sym)
if not m:
new_line.append(sym)
continue
func_idx = int(m.groups()[-1]) + import_function_count
if func_idx in name_section:
wasm_func_name = f"[Wasm] {name_section[func_idx]}"
else:
wasm_func_name = (
f"[Wasm] function[{func_idx + import_function_count}]"
)
if precheck_mode:
# aot_func_internal -> xxx
# aot_func --> xxx_precheck
wasm_func_name += "_precheck" if not m.groups()[0] else ""
else:
# aot_func --> xxx
pass
new_line.append(wasm_func_name)
line = ";".join(new_line)
line += f" {samples}"
f_out.write(line + os.linesep)
print(f"⚙️ {folded_in} -> {folded_out}")
def main(wabt_home: str, wasm_file: str, folded: str) -> None:
wabt_home = Path(wabt_home)
wasm_file = Path(wasm_file)
wasm_objdump_bin = preflight_check(wabt_home)
import_section = collect_import_section_content(wasm_objdump_bin, wasm_file)
name_section = collect_name_section_content(wasm_objdump_bin, wasm_file)
replace_function_name(import_section, name_section, folded, folded + ".translated")
if __name__ == "__main__":
argparse = argparse.ArgumentParser()
argparse.add_argument(
"--folded", help="stackcollapse-perf.pl generated, like out.folded"
)
argparse.add_argument("wasm_file", help="wasm file")
argparse.add_argument("--wabt_home", help="wabt home, like /opt/wabt-1.0.33")
args = argparse.parse_args()
main(args.wabt_home, args.wasm_file, args.folded)