From 86f1558b34ae1ca05fba0388617f5dee92db518e Mon Sep 17 00:00:00 2001 From: Vsevolod Pukhov Date: Fri, 13 Jun 2025 13:49:20 +0300 Subject: [PATCH] PerfMetrics performance tracking tool Introduce a simple thread-safe c++ tool to gather frontend-specific performance metrics Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICF3KL Signed-off-by: Vsevolod Pukhov --- ets2panda/BUILD.gn | 1 + ets2panda/CMakeLists.txt | 1 + ets2panda/aot/main.cpp | 36 +-- ets2panda/compiler/core/compilerImpl.cpp | 4 + ets2panda/util/generateBin.cpp | 9 +- ets2panda/util/options.yaml | 5 + ets2panda/util/perfMetrics.cpp | 272 +++++++++++++++++++++++ ets2panda/util/perfMetrics.h | 68 ++++++ 8 files changed, 379 insertions(+), 17 deletions(-) create mode 100644 ets2panda/util/perfMetrics.cpp create mode 100644 ets2panda/util/perfMetrics.h diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 42c9d7f055..c5a1b54a91 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -492,6 +492,7 @@ libes2panda_sources = [ "util/helpers.cpp", "util/importPathManager.cpp", "util/path.cpp", + "util/perfMetrics.cpp", "util/plugin.cpp", "util/ustring.cpp", "varbinder/ASBinder.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 695f3a76f9..75fd890139 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -646,6 +646,7 @@ set(ES2PANDA_LIB_SRC util/helpers.cpp util/importPathManager.cpp util/path.cpp + util/perfMetrics.cpp util/ustring.cpp test/utils/panda_executable_path_getter.cpp evaluate/debugInfoDeserialization/debugInfoDeserializer.cpp diff --git a/ets2panda/aot/main.cpp b/ets2panda/aot/main.cpp index 411df8364a..510014c943 100644 --- a/ets2panda/aot/main.cpp +++ b/ets2panda/aot/main.cpp @@ -24,6 +24,7 @@ #include "util/generateBin.h" #include "util/options.h" #include "util/plugin.h" +#include "util/perfMetrics.h" #include "libpandabase/os/stacktrace.h" #include "generated/diagnostic.h" @@ -100,8 +101,8 @@ static int CompileMultipleFiles(es2panda::Compiler &compiler, std::vectorArkTSConfig()); if (compilationList.empty()) { @@ -185,22 +186,27 @@ static int Run(Span args) return 1; } + int res; if (options->GetCompilationMode() == CompilationMode::PROJECT) { - return CompileFromConfig(compiler, options.get(), diagnosticEngine); - } - - std::string sourceFile; - std::string_view parserInput; - if (options->GetCompilationMode() == CompilationMode::GEN_STD_LIB) { - sourceFile = "etsstdlib.ets"; - parserInput = ""; + res = CompileFromConfig(compiler, options.get(), diagnosticEngine); } else { - sourceFile = options->SourceFileName(); - auto [buf, size] = options->CStrParserInputContents(); - parserInput = std::string_view(buf, size); + std::string sourceFile; + std::string_view parserInput; + if (options->GetCompilationMode() == CompilationMode::GEN_STD_LIB) { + sourceFile = "etsstdlib.ets"; + parserInput = ""; + } else { + sourceFile = options->SourceFileName(); + auto [buf, size] = options->CStrParserInputContents(); + parserInput = std::string_view(buf, size); + } + es2panda::SourceFile input(sourceFile, parserInput, options->IsModule(), options->GetOutput()); + res = CompileFromSource(compiler, input, *options.get(), diagnosticEngine); + } + if (options->IsDumpPerfMetrics()) { + util::DumpPerfMetrics(); } - es2panda::SourceFile input(sourceFile, parserInput, options->IsModule(), options->GetOutput()); - return CompileFromSource(compiler, input, *options.get(), diagnosticEngine); + return res; } } // namespace ark::es2panda::aot diff --git a/ets2panda/compiler/core/compilerImpl.cpp b/ets2panda/compiler/core/compilerImpl.cpp index 7086b2707d..d7f6065bf3 100644 --- a/ets2panda/compiler/core/compilerImpl.cpp +++ b/ets2panda/compiler/core/compilerImpl.cpp @@ -47,6 +47,7 @@ #include "parser/program/program.h" #include "public/public.h" #include "util/ustring.h" +#include "util/perfMetrics.h" #include "varbinder/JSBinder.h" #include "varbinder/ASBinder.h" #include "varbinder/TSBinder.h" @@ -197,6 +198,7 @@ static bool RunVerifierAndPhases(public_lib::Context &context, parser::Program & if (name == compiler::CheckerPhase::NAME) { afterCheckerPhase = true; } + ES2PANDA_PERF_EVENT_SCOPE("phases/" + name); if (CheckIfPhaseToSkip(options, name)) { continue; @@ -403,6 +405,7 @@ static void SavePermanents(public_lib::Context *ctx, parser::Program *program) static pandasm::Program *EmitProgram(CompilerImpl *compilerImpl, public_lib::Context *context, const CompilationUnit &unit) { + ES2PANDA_PERF_SCOPE("EmitProgram"); context->emitter->GenAnnotation(); auto result = compilerImpl->Emit(context); if (unit.ext == ScriptExtension::ETS && context->compilingState != public_lib::CompilingState::SINGLE_COMPILING) { @@ -413,6 +416,7 @@ static pandasm::Program *EmitProgram(CompilerImpl *compilerImpl, public_lib::Con static bool ExecuteParsingAndCompiling(const CompilationUnit &unit, public_lib::Context *context) { + ES2PANDA_PERF_SCOPE("Parser and pipeline phases"); parser::Program *program = context->parserProgram; if (unit.ext == ScriptExtension::ETS && context->compilingState == public_lib::CompilingState::MULTI_COMPILING_FOLLOW) { diff --git a/ets2panda/util/generateBin.cpp b/ets2panda/util/generateBin.cpp index 33abd21586..028875d16a 100644 --- a/ets2panda/util/generateBin.cpp +++ b/ets2panda/util/generateBin.cpp @@ -19,6 +19,7 @@ #include "compiler/compiler_logger.h" #include "compiler/compiler_options.h" #include "util/options.h" +#include "util/perfMetrics.h" namespace ark::es2panda::util { @@ -103,14 +104,18 @@ static int GenerateProgramImpl(ark::pandasm::Program *prog, const util::Options int GenerateProgram(ark::pandasm::Program *prog, const util::Options &options, const ReporterFun &reporter) { + ES2PANDA_PERF_SCOPE("GenerateProgram"); std::map stat; std::map *statp = options.GetOptLevel() != 0 ? &stat : nullptr; ark::pandasm::AsmEmitter::PandaFileToPandaAsmMaps maps {}; ark::pandasm::AsmEmitter::PandaFileToPandaAsmMaps *mapsp = options.GetOptLevel() != 0 ? &maps : nullptr; #ifdef PANDA_WITH_BYTECODE_OPTIMIZER - if (OptimizeBytecode(prog, options, reporter, statp, mapsp) != 0) { - return 1; + { + ES2PANDA_PERF_SCOPE("GenerateProgram/OptimizeBytecode"); + if (OptimizeBytecode(prog, options, reporter, statp, mapsp) != 0) { + return 1; + } } #endif if (GenerateProgramImpl(prog, options, reporter, statp, mapsp) != 0) { diff --git a/ets2panda/util/options.yaml b/ets2panda/util/options.yaml index 2a1894aeb7..5eae0785f3 100644 --- a/ets2panda/util/options.yaml +++ b/ets2panda/util/options.yaml @@ -378,3 +378,8 @@ options: type: bool default: false description: Place AST trees in permanent arena + +- name: dump-perf-metrics + type: bool + default: false + description: Dump es2panda performance metrics diff --git a/ets2panda/util/perfMetrics.cpp b/ets2panda/util/perfMetrics.cpp new file mode 100644 index 0000000000..7d207d956f --- /dev/null +++ b/ets2panda/util/perfMetrics.cpp @@ -0,0 +1,272 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "util/perfMetrics.h" + +#include "libpandabase/utils/logger.h" +#include "libpandabase/utils/type_converter.h" +#include "libpandabase/mem/mem.h" +#include +#include +#include + +#ifdef PANDA_TARGET_UNIX +#include "sys/resource.h" +#endif + +namespace ark::es2panda::util { + +static int64_t PerfMetricsGetTimeNS() +{ + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); +} + +[[maybe_unused]] static int64_t PerfMetricsGetMaxRSS() +{ +#ifdef PANDA_TARGET_UNIX + struct rusage ru {}; + if (getrusage(RUSAGE_SELF, &ru) != 0) { + LOG(FATAL, ES2PANDA) << "getrusage failed"; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + return static_cast(ru.ru_maxrss) * 1_KB; +#else + return 0; +#endif +} + +class PerfMetricValue { +public: + static PerfMetricValue Zero() + { + return PerfMetricValue(); + } + + static PerfMetricValue Now() + { + PerfMetricValue st; + st.time_ = PerfMetricsGetTimeNS(); + st.mem_ = PerfMetricsGetMaxRSS(); + return st; + } + + static PerfMetricValue Accumulate(PerfMetricValue const &acc, PerfMetricValue const &begin, + PerfMetricValue const &end) + { + PerfMetricValue diff = Combine(end, begin, false); + return Combine(acc, diff, true); + } + + int64_t GetTimeNanoseconds() const + { + return time_; + } + + int64_t GetMemorySize() const + { + return mem_; + } + + DEFAULT_COPY_SEMANTIC(PerfMetricValue); + DEFAULT_MOVE_SEMANTIC(PerfMetricValue); + ~PerfMetricValue() = default; + +private: + PerfMetricValue() = default; + + static PerfMetricValue Combine(PerfMetricValue const &st1, PerfMetricValue const &st2, bool sum) + { + int mult = sum ? 1 : -1; + PerfMetricValue res; + res.time_ = st1.time_ + mult * st2.time_; + res.mem_ = st1.mem_ + mult * st2.mem_; + return res; + } + + int64_t time_ {}; + int64_t mem_ {}; +}; + +class PerfMetricRecord { +public: + explicit PerfMetricRecord(std::string_view name) : name_(name) {} + + PerfMetricValue const &GetStats() const + { + return acc_; + } + + std::string const &GetName() const + { + return name_; + } + + void BeginTrace() + { + invokationsCount_++; + if (nesting_ == 0) { + begin_ = PerfMetricValue::Now(); + } + if (++nesting_ > maxNesting_) { + maxNesting_ = nesting_; + } + } + + void EndTrace() + { + if (--nesting_ == 0) { + acc_ = PerfMetricValue::Accumulate(acc_, begin_, PerfMetricValue::Now()); + } + } + + size_t GetMaxNesting() const + { + return maxNesting_; + } + + size_t GetInvokationsCount() const + { + return invokationsCount_; + } + + NO_COPY_SEMANTIC(PerfMetricRecord); + DEFAULT_MOVE_SEMANTIC(PerfMetricRecord); + ~PerfMetricRecord() = default; + +private: + size_t nesting_ {}; + size_t maxNesting_ {}; + size_t invokationsCount_ {}; + std::string name_; + PerfMetricValue acc_ = PerfMetricValue::Zero(); + PerfMetricValue begin_ = PerfMetricValue::Zero(); +}; + +PerfMetricScope::PerfMetricScope(PerfMetricRecord *record) : record_(record) +{ + record_->BeginTrace(); +} + +PerfMetricScope::~PerfMetricScope() +{ + record_->EndTrace(); +} + +// NOLINTBEGIN(fuchsia-statically-constructed-objects) +static std::forward_list>> g_perfMetricsLists; +static std::mutex g_perfMetricsListsMtx; +// NOLINTEND(fuchsia-statically-constructed-objects) + +std::forward_list *GetTLSPerfMetricsData() +{ + thread_local auto tls = ([]() { + std::lock_guard lk(g_perfMetricsListsMtx); + g_perfMetricsLists.push_front({os::thread::GetCurrentThreadId(), std::forward_list()}); + return &g_perfMetricsLists.begin()->second; + })(); + + return tls; +} + +PerfMetricRecord *RegisterPerfMetricRecord(std::string_view name) +{ + auto *tls = GetTLSPerfMetricsData(); + tls->emplace_front(PerfMetricRecord(name)); + return &tls->front(); +} + +static std::string PrettyTimeNs(uint64_t duration) +{ + // CC-OFF(G.NAM.03-CPP) project code style + constexpr std::array TIME_UNITS = {"ns", "us", "ms", "s"}; + // CC-OFF(G.NAM.03-CPP) project code style + constexpr double K_SCALE = 1E3; + + if (duration == 0) { + return "0s"; + } + + double val = duration; + size_t unitIdx = 0; + + for (;; ++unitIdx) { + double nextVal = val / K_SCALE; + if (unitIdx == TIME_UNITS.size() || nextVal < 1.0) { + break; + } + val = nextVal; + } + + std::stringstream ss; + ss << std::setprecision(3U) << val << TIME_UNITS[unitIdx]; + return ss.str(); +} + +static void DumpPerfMetricRecord(std::stringstream &ss, PerfMetricRecord const *rec) +{ + // CC-OFFNXT(G.FMT.14-CPP) project code style + auto const metric = [&ss](std::string_view name, unsigned w) -> std::stringstream & { + ss << " " << name << "=" << std::left << std::setw(w); + return ss; + }; + + auto const &stats = rec->GetStats(); + // NOLINTBEGIN(readability-magic-numbers) + ss << "#" << std::left << std::setw(50U) << rec->GetName() << ": "; + if (rec->GetMaxNesting() > 1) { + metric("nesting", 6U) << rec->GetMaxNesting(); + } + if (rec->GetInvokationsCount() > 1) { + metric("count", 8U) << rec->GetInvokationsCount(); + } + metric("time", 10U) << PrettyTimeNs(stats.GetTimeNanoseconds()); + auto memValue = ark::helpers::MemoryConverter(stats.GetMemorySize()); + metric("mem", 10U) << (std::to_string(static_cast(memValue.GetDoubleValue())) + + std::string(memValue.GetLiteral())); + // NOLINTEND(readability-magic-numbers) +} + +// You may want to add __attribute__((destructor)) for the debugging needs +void DumpPerfMetrics() +{ + std::lock_guard lk(g_perfMetricsListsMtx); // prevents new thread from appearing + + auto getSortVal = [](PerfMetricRecord *rec) { return rec->GetStats().GetTimeNanoseconds(); }; + + std::stringstream ss; + ss << "============================ es2panda perf metrics =============================" << std::endl; + + for (auto &tlsdata : g_perfMetricsLists) { + std::vector records; + for (auto &rec : tlsdata.second) { + records.push_back(&rec); + } + std::sort(records.begin(), records.end(), + [getSortVal](PerfMetricRecord *r1, PerfMetricRecord *r2) { return getSortVal(r1) > getSortVal(r2); }); + + ss << "==== Thread [" << tlsdata.first << "]" << std::endl; + + for (auto &rec : records) { + DumpPerfMetricRecord(ss, rec); + ss << std::endl; + } + } + + std::cout << ss.str(); +} + +} // namespace ark::es2panda::util diff --git a/ets2panda/util/perfMetrics.h b/ets2panda/util/perfMetrics.h new file mode 100644 index 0000000000..37f72ffbca --- /dev/null +++ b/ets2panda/util/perfMetrics.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ES2PANDA_UTIL_PERF_METRICS_H +#define ES2PANDA_UTIL_PERF_METRICS_H + +#include "libpandabase/macros.h" + +namespace ark::es2panda::util { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define ES2PANDA_PERF_CONCAT2_(a, b) a##b +// CC-OFFNXT(G.PRE.02-CPP) macro definition +#define ES2PANDA_PERF_CONCAT2(a, b) ES2PANDA_PERF_CONCAT2_(a, b) + +// Opens a RAII scope of performance tracking. RAII scope is associated with a single +// unique record, which accumulates metrics on each subsequent invokation. +// CC-OFFNXT(G.PRE.02-CPP) macro definition +#define ES2PANDA_PERF_SCOPE(name) \ + thread_local auto ES2PANDA_PERF_CONCAT2(e2pPerfRecord, __LINE__) = \ + ark::es2panda::util::RegisterPerfMetricRecord(name); \ + ark::es2panda::util::PerfMetricScope ES2PANDA_PERF_CONCAT2(e2pPerfScope, __LINE__)( \ + ES2PANDA_PERF_CONCAT2(e2pPerfRecord, __LINE__)) + +// Similar to ES2PANDA_PERF_SCOPE, the current functions name is assigned to the performance record. +// CC-OFFNXT(G.PRE.02-CPP) macro definition +#define ES2PANDA_PERF_FN_SCOPE() ES2PANDA_PERF_SCOPE(__PRETTY_FUNCTION__) + +// Opens a RAII scope of performance tracking. Each subsequent invokation is associated with +// a new performance record. Do not use it if scope is created frequently. +// CC-OFFNXT(G.PRE.02-CPP) macro definition +#define ES2PANDA_PERF_EVENT_SCOPE(name) \ + ark::es2panda::util::PerfMetricScope e2pPerfScope(ark::es2panda::util::RegisterPerfMetricRecord(name)) +// NOLINTEND(cppcoreguidelines-macro-usage) + +// Dump all the collected performance records. Should not be called if there are active threads using the profile. +void DumpPerfMetrics(); + +class PerfMetricRecord; +PerfMetricRecord *RegisterPerfMetricRecord(std::string_view name); + +class PerfMetricScope { +public: + explicit PerfMetricScope(PerfMetricRecord *record); + ~PerfMetricScope(); + + NO_COPY_SEMANTIC(PerfMetricScope); + NO_MOVE_SEMANTIC(PerfMetricScope); + +private: + PerfMetricRecord *record_; +}; + +} // namespace ark::es2panda::util + +#endif -- Gitee