LibWasm+LibWeb: Add per-module wasm compile stats
This commit is contained in:
parent
2419cce8d8
commit
842e8a6796
8 changed files with 130 additions and 2 deletions
|
|
@ -16,6 +16,63 @@
|
|||
|
||||
namespace Wasm {
|
||||
|
||||
static Vector<ModuleStats> s_module_stats;
|
||||
|
||||
void record_module_stats(ModuleStats stats)
|
||||
{
|
||||
s_module_stats.append(move(stats));
|
||||
}
|
||||
|
||||
void dump_module_stats()
|
||||
{
|
||||
if (s_module_stats.is_empty()) {
|
||||
warnln("wasm-stats: no modules compiled yet");
|
||||
return;
|
||||
}
|
||||
|
||||
warnln("wasm-stats: {} module(s) compiled", s_module_stats.size());
|
||||
warnln("wasm-stats: hash input KiB parse ms validate ms cl ms cl blob KiB funcs cache");
|
||||
|
||||
AK::Duration total_parse;
|
||||
AK::Duration total_validate;
|
||||
AK::Duration total_cranelift;
|
||||
size_t total_input = 0;
|
||||
size_t total_blob = 0;
|
||||
size_t total_hits = 0;
|
||||
|
||||
for (auto const& s : s_module_stats) {
|
||||
StringBuilder hash_prefix;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
hash_prefix.appendff("{:02x}", s.wasm_hash[i]);
|
||||
|
||||
warnln("wasm-stats: {} {:>9} {:>8} {:>11} {:>5} {:>11} {:>5} {}",
|
||||
hash_prefix.to_byte_string(),
|
||||
s.input_size_bytes / 1024,
|
||||
s.parse_time.to_milliseconds(),
|
||||
s.validate_time.to_milliseconds(),
|
||||
s.cranelift_time.to_milliseconds(),
|
||||
s.cranelift_blob_size_bytes / 1024,
|
||||
s.function_count,
|
||||
s.cache_hit ? "HIT" : "miss");
|
||||
|
||||
total_parse = total_parse + s.parse_time;
|
||||
total_validate = total_validate + s.validate_time;
|
||||
total_cranelift = total_cranelift + s.cranelift_time;
|
||||
total_input += s.input_size_bytes;
|
||||
total_blob += s.cranelift_blob_size_bytes;
|
||||
if (s.cache_hit)
|
||||
++total_hits;
|
||||
}
|
||||
|
||||
warnln("wasm-stats: ---- {:>9} {:>8} {:>11} {:>5} {:>11} hits={}",
|
||||
total_input / 1024,
|
||||
total_parse.to_milliseconds(),
|
||||
total_validate.to_milliseconds(),
|
||||
total_cranelift.to_milliseconds(),
|
||||
total_blob / 1024,
|
||||
total_hits);
|
||||
}
|
||||
|
||||
MemoryBuffer::~MemoryBuffer()
|
||||
{
|
||||
clear();
|
||||
|
|
|
|||
|
|
@ -281,16 +281,24 @@ ErrorOr<void, ValidationError> Validator::validate(CodeSection const& section)
|
|||
begin_cranelift_cache_capture();
|
||||
capturing = true;
|
||||
}
|
||||
if (m_cache_config->out_cache_hit)
|
||||
*m_cache_config->out_cache_hit = installing;
|
||||
}
|
||||
|
||||
ScopeGuard cleanup = [&] {
|
||||
auto cranelift_start = MonotonicTime::now();
|
||||
flush_cranelift_batch();
|
||||
auto cranelift_duration = MonotonicTime::now() - cranelift_start;
|
||||
|
||||
if (installing)
|
||||
abort_cranelift_cache_install();
|
||||
|
||||
size_t produced_blob_size = 0;
|
||||
if (capturing) {
|
||||
if (validation_succeeded) {
|
||||
auto hash = ReadonlyBytes { m_cache_config->wasm_hash.data(), 32 };
|
||||
if (auto blob = serialize_cranelift_cache_blob(hash); blob.has_value() && m_cache_config->on_compiled) {
|
||||
produced_blob_size = blob->size();
|
||||
m_cache_config->on_compiled(blob.release_value());
|
||||
} else {
|
||||
abort_cranelift_cache_capture();
|
||||
|
|
@ -299,6 +307,22 @@ ErrorOr<void, ValidationError> Validator::validate(CodeSection const& section)
|
|||
abort_cranelift_cache_capture();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_cache_config.has_value()) {
|
||||
if (m_cache_config->out_cranelift_time)
|
||||
*m_cache_config->out_cranelift_time = cranelift_duration;
|
||||
if (m_cache_config->out_cranelift_blob_size_bytes)
|
||||
*m_cache_config->out_cranelift_blob_size_bytes = produced_blob_size;
|
||||
if (m_cache_config->out_function_count) {
|
||||
size_t count = 0;
|
||||
for (auto& entry : section.functions()) {
|
||||
if (entry.func().body().compiled_instructions.cranelift_compiled)
|
||||
++count;
|
||||
}
|
||||
*m_cache_config->out_function_count = count;
|
||||
}
|
||||
}
|
||||
|
||||
set_cranelift_active_function_index(NumericLimits<u32>::max());
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Result.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/UFixedBigInt.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
|
|
@ -1555,12 +1556,34 @@ void flush_cranelift_batch();
|
|||
// to install it before falling through to cranelift.
|
||||
// - `on_compiled` is invoked exactly once if a fresh blob was produced; never
|
||||
// invoked on a cache hit nor when no cranelift output was captured.
|
||||
// - The `out_*` fields, if non-null, receive measurements taken during
|
||||
// validate(CodeSection). Used by the LibWeb side to populate ModuleStats.
|
||||
struct CompileCacheConfig {
|
||||
Array<u8, 32> wasm_hash {};
|
||||
ReadonlyBytes existing_blob;
|
||||
AK::Function<void(ByteBuffer)> on_compiled;
|
||||
|
||||
AK::Duration* out_cranelift_time { nullptr };
|
||||
size_t* out_function_count { nullptr };
|
||||
size_t* out_cranelift_blob_size_bytes { nullptr };
|
||||
bool* out_cache_hit { nullptr };
|
||||
};
|
||||
|
||||
// Lightweight per-module compile stats accumulator. Exposed to embedders via record_module_stats() below.
|
||||
struct ModuleStats {
|
||||
Array<u8, 32> wasm_hash {};
|
||||
size_t input_size_bytes { 0 };
|
||||
AK::Duration parse_time;
|
||||
AK::Duration validate_time;
|
||||
AK::Duration cranelift_time;
|
||||
size_t cranelift_blob_size_bytes { 0 };
|
||||
size_t function_count { 0 };
|
||||
bool cache_hit { false };
|
||||
};
|
||||
|
||||
WASM_API void record_module_stats(ModuleStats);
|
||||
WASM_API void dump_module_stats();
|
||||
|
||||
// Cranelift disk-cache plumbing. Validator drives these around CodeSection validation:
|
||||
// 1. set_cranelift_active_function_index() before each function so cache-hit installs
|
||||
// and post-compile capture know which function they're talking about.
|
||||
|
|
|
|||
|
|
@ -432,8 +432,13 @@ JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webass
|
|||
{
|
||||
TRY(host_ensure_can_compile_wasm_bytes(vm));
|
||||
|
||||
Wasm::ModuleStats stats;
|
||||
stats.input_size_bytes = data.size();
|
||||
|
||||
auto parse_start = MonotonicTime::now();
|
||||
FixedMemoryStream stream { data.bytes() };
|
||||
auto module_result = Wasm::Module::parse(stream);
|
||||
stats.parse_time = MonotonicTime::now() - parse_start;
|
||||
if (module_result.is_error()) {
|
||||
return vm.throw_completion<CompileError>(Wasm::parse_error_to_byte_string(module_result.error()));
|
||||
}
|
||||
|
|
@ -447,6 +452,7 @@ JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webass
|
|||
Optional<Wasm::CompileCacheConfig> wasm_cache_config;
|
||||
if (ResourceLoader::is_initialized() && ResourceLoader::the().request_client()) {
|
||||
auto digest = ::Crypto::Hash::SHA256::hash(data.data(), data.size());
|
||||
__builtin_memcpy(stats.wasm_hash.data(), digest.bytes().data(), 32);
|
||||
|
||||
StringBuilder hex_builder;
|
||||
for (auto byte : digest.bytes())
|
||||
|
|
@ -476,12 +482,22 @@ JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webass
|
|||
url, method, OptionalNone {}, 0u,
|
||||
HTTP::CacheEntryAssociatedData::WebAssemblyCompiledCode, blob.bytes());
|
||||
};
|
||||
|
||||
config.out_cranelift_time = &stats.cranelift_time;
|
||||
config.out_function_count = &stats.function_count;
|
||||
config.out_cranelift_blob_size_bytes = &stats.cranelift_blob_size_bytes;
|
||||
config.out_cache_hit = &stats.cache_hit;
|
||||
wasm_cache_config = move(config);
|
||||
}
|
||||
}
|
||||
|
||||
auto& cache = get_cache(*vm.current_realm());
|
||||
if (auto validation_result = cache.abstract_machine().validate(module_result.value(), move(wasm_cache_config)); validation_result.is_error()) {
|
||||
auto validate_start = MonotonicTime::now();
|
||||
auto validation_result = cache.abstract_machine().validate(module_result.value(), move(wasm_cache_config));
|
||||
stats.validate_time = MonotonicTime::now() - validate_start;
|
||||
Wasm::record_module_stats(stats);
|
||||
|
||||
if (validation_result.is_error()) {
|
||||
return vm.throw_completion<CompileError>(validation_result.error().error_string);
|
||||
}
|
||||
auto compiled_module = make_ref_counted<CompiledWebAssemblyModule>(module_result.release_value());
|
||||
|
|
|
|||
|
|
@ -1455,6 +1455,7 @@ void Application::initialize_actions()
|
|||
m_debug_menu->add_action(Action::create("Dump CSS Errors"sv, ActionID::DumpCSSErrors, debug_request("dump-all-css-errors"sv)));
|
||||
m_debug_menu->add_action(Action::create("Dump Cookies"sv, ActionID::DumpCookies, [this]() { m_cookie_jar->dump_cookies(); }));
|
||||
m_debug_menu->add_action(Action::create("Dump Local Storage"sv, ActionID::DumpLocalStorage, debug_request("dump-local-storage"sv)));
|
||||
m_debug_menu->add_action(Action::create("Dump WASM Stats"sv, ActionID::DumpWasmStats, debug_request("dump-wasm-stats"sv)));
|
||||
m_debug_menu->add_action(Action::create("Dump GC graph"sv, ActionID::DumpGCGraph, [this]() {
|
||||
if (auto view = active_web_view(); view.has_value()) {
|
||||
auto gc_graph_path = view->dump_gc_graph();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ enum class ActionID {
|
|||
DumpCookies,
|
||||
DumpLocalStorage,
|
||||
DumpGCGraph,
|
||||
DumpWasmStats,
|
||||
ShowLineBoxBorders,
|
||||
CollectGarbage,
|
||||
SpoofUserAgent,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ target_include_directories(webcontentservice PUBLIC $<BUILD_INTERFACE:${CMAKE_CU
|
|||
target_include_directories(webcontentservice PUBLIC $<BUILD_INTERFACE:${LADYBIRD_SOURCE_DIR}>)
|
||||
target_include_directories(webcontentservice PUBLIC $<BUILD_INTERFACE:${LADYBIRD_SOURCE_DIR}/Services/>)
|
||||
|
||||
target_link_libraries(webcontentservice PUBLIC LibCore LibCrypto LibFileSystem LibGfx LibHTTP LibIPC LibJS LibMain LibMedia LibWeb LibWebSocket LibRequests LibWebView LibImageDecoderClient LibGC)
|
||||
target_link_libraries(webcontentservice PUBLIC LibCore LibCrypto LibFileSystem LibGfx LibHTTP LibIPC LibJS LibMain LibMedia LibWasm LibWeb LibWebSocket LibRequests LibWebView LibImageDecoderClient LibGC)
|
||||
target_link_libraries(webcontentservice PRIVATE OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(webcontentservice PRIVATE SDL3::SDL3)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <LibJS/Runtime/ConsoleObject.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibUnicode/TimeZone.h>
|
||||
#include <LibWasm/Types.h>
|
||||
#include <LibWeb/ARIA/RoleType.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
|
|
@ -428,6 +429,11 @@ void ConnectionFromClient::debug_request(u64 page_id, ByteString request, ByteSt
|
|||
return;
|
||||
}
|
||||
|
||||
if (request == "dump-wasm-stats") {
|
||||
Wasm::dump_module_stats();
|
||||
return;
|
||||
}
|
||||
|
||||
if (request == "collect-garbage") {
|
||||
// NOTE: We use deferred_invoke here to ensure that GC runs with as little on the stack as possible.
|
||||
Core::deferred_invoke([] {
|
||||
|
|
|
|||
Loading…
Reference in a new issue