mirror of
https://github.com/bytecodealliance/wasm-micro-runtime.git
synced 2024-11-26 07:21:54 +00:00
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:
parent
cee9b826a5
commit
4ef724bbff
|
@ -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;
|
||||
|
|
120
core/iwasm/aot/aot_perf_map.c
Normal file
120
core/iwasm/aot/aot_perf_map.c
Normal 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 */
|
15
core/iwasm/aot/aot_perf_map.h
Normal file
15
core/iwasm/aot/aot_perf_map.h
Normal 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_ */
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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,
|
||||
|
|
63
samples/linux-perf/CMakeLists.txt
Normal file
63
samples/linux-perf/CMakeLists.txt
Normal 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}
|
||||
)
|
90
samples/linux-perf/README.md
Normal file
90
samples/linux-perf/README.md
Normal 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)
|
14
samples/linux-perf/cmake/FindWAMRC.cmake
Normal file
14
samples/linux-perf/cmake/FindWAMRC.cmake
Normal 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)
|
23
samples/linux-perf/cmake/FindWASISDK.cmake
Normal file
23
samples/linux-perf/cmake/FindWASISDK.cmake
Normal 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()
|
198
samples/linux-perf/host/demo.c
Normal file
198
samples/linux-perf/host/demo.c
Normal 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, ¶ms, &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;
|
||||
}
|
1349
samples/linux-perf/pics/perf.ackermann.svg
Normal file
1349
samples/linux-perf/pics/perf.ackermann.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 64 KiB |
605
samples/linux-perf/pics/perf.fib.svg
Normal file
605
samples/linux-perf/pics/perf.fib.svg
Normal 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
BIN
samples/linux-perf/pics/perf.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
42
samples/linux-perf/wasm/CMakeLists.txt
Normal file
42
samples/linux-perf/wasm/CMakeLists.txt
Normal 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 .)
|
38
samples/linux-perf/wasm/ackermann.c
Normal file
38
samples/linux-perf/wasm/ackermann.c
Normal 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;
|
||||
}
|
32
samples/linux-perf/wasm/fib.c
Normal file
32
samples/linux-perf/wasm/fib.c
Normal 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;
|
||||
}
|
2
test-tools/flame-graph-helper/.gitignore
vendored
Normal file
2
test-tools/flame-graph-helper/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.*
|
||||
!*.py
|
325
test-tools/flame-graph-helper/process_folded_data.py
Normal file
325
test-tools/flame-graph-helper/process_folded_data.py
Normal 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)
|
|
@ -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)
|
Loading…
Reference in New Issue
Block a user