Skip to content

feat: add perf trace log channel#17

Open
weypro wants to merge 1 commit into
MaaXYZ:masterfrom
weypro:dev/perfnew
Open

feat: add perf trace log channel#17
weypro wants to merge 1 commit into
MaaXYZ:masterfrom
weypro:dev/perfnew

Conversation

@weypro
Copy link
Copy Markdown

@weypro weypro commented May 10, 2026

Summary by Sourcery

引入一个专用的性能追踪日志通道,用于将 CSV 格式的追踪数据写入一个独立的文件,与现有日志记录器并行存在。

新功能:

  • 添加 PerfLogStream 类型,用于缓冲、按行输出性能追踪日志,不带标准日志前缀,也不写入 stdout。
  • 暴露 LogPerf 宏和 Logger::perf_stream() 入口,用于将原始 CSV 性能追踪行写入专用的性能日志文件。

增强改进:

  • Logger 中管理独立的性能追踪文件生命周期,包括初始化、目录管理,以及通过专用互斥锁保护的安全并发写入。
  • 扩展日志文件清理逻辑以同时处理 .csv 性能追踪文件,并确保在日志记录器关闭时正确关闭性能日志。
Original summary in English

Summary by Sourcery

Introduce a dedicated performance trace logging channel that writes CSV-formatted trace data to a separate file alongside the existing logger.

New Features:

  • Add a PerfLogStream type for buffered, line-based performance trace output without standard log prefixes or stdout writes.
  • Expose a LogPerf macro and Logger::perf_stream() entrypoint for writing raw CSV performance trace lines to a dedicated perf log file.

Enhancements:

  • Manage a separate perf trace file lifecycle in Logger, including initialization, directory management, and safe concurrent writes guarded by dedicated mutexes.
  • Extend log file cleanup to also handle .csv performance trace files and ensure perf logs are properly closed when the logger shuts down.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体性的反馈:

  • 建议对 PerfLogStream 显式删除移动构造函数(或自定义一个移动构造函数),因为当前带有引用成员并使用 = default 的移动构造很容易导致多个存活对象引用同一个流/互斥量,从而造成令人困惑的重复写入或 use-after-move 模式。
  • perf_try_open_locked 的 Windows 特定分支中,使用 FILE* 来构造 std::ofstream,这并不是标准 C++ 所定义的构造方式;为了更安全并具备更好的可移植性,建议要么结合 _open_osfhandle/_fdopen 和文件描述符,然后通过路径传给 std::ofstream,要么以其他方式重构这段代码,使其依然能清除句柄继承,但对外只依赖标准的 ofstream API。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider explicitly deleting the move constructor (or defining a custom one) for `PerfLogStream`, since the current `= default` move with reference members can easily lead to multiple live objects referring to the same stream/mutex and cause confusing double-writes or use-after-move patterns.
- The Windows-specific branch in `perf_try_open_locked` constructs `std::ofstream` from a `FILE*`, which is not a standard C++ constructor; it would be safer and more portable to either use `_open_osfhandle`/`_fdopen` with a file descriptor and then pass a path to `std::ofstream`, or otherwise rework this to rely on the standard `ofstream` API while still clearing handle inheritance.

## Individual Comments

### Comment 1
<location path="include/MaaUtils/LoggerUtils.h" line_range="182-186" />
<code_context>
+    PerfLogStream(const PerfLogStream&) = delete;
+    PerfLogStream(PerfLogStream&&) noexcept = default;
+
+    ~PerfLogStream()
+    {
+        std::unique_lock lock(mutex_);
+        if (stream_.is_open()) {
+            stream_ << buffer_.str() << '\n';
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** 建议将 PerfLogStream 的析构函数标记为 `noexcept`,并对流写入过程中的异常做保护。

这个析构函数在栈展开过程中执行流写操作,但没有标记为 `noexcept`。如果底层流抛出异常(例如通过 `exceptions()`/`badbit`),可能会导致调用 `std::terminate`。对于日志/性能统计这类工具,通常更希望将 I/O 失败视为非致命问题:可以将写入逻辑包裹在吞掉错误的 `try/catch` 中,并将析构函数标记为 `noexcept`,以避免因为日志写入失败而导致程序崩溃。
</issue_to_address>

### Comment 2
<location path="source/Logger/Logger.cpp" line_range="338-345" />
<code_context>
+        if (perf_ofs_.is_open()) {
+            perf_ofs_.close();
+        }
+#ifdef _WIN32
+        std::string str_path = perf_path.string();
+        FILE* fp = fopen(str_path.c_str(), "w");
+        if (!fp) {
+            return;
+        }
+        SetHandleInformation((HANDLE)_get_osfhandle(_fileno(fp)), HANDLE_FLAG_INHERIT, 0);
+        perf_ofs_ = std::ofstream(fp);
+#else
+        perf_ofs_ = std::ofstream(perf_path, std::ios::out | std::ios::trunc);
</code_context>
<issue_to_address>
**issue (bug_risk):** 直接从 `FILE*` 构造 `std::ofstream` 是非标准用法,可能会带来可移植性和所有权问题。

在 Windows 上,`perf_ofs_ = std::ofstream(fp);` 有问题,因为标准库并没有定义接受 `FILE*``ofstream` 构造函数。这很可能依赖于某个不可移植的扩展,并且会让所有权关系变得不清晰——不确定是 `fp` 还是 `perf_ofs_` 拥有该句柄(有双重关闭或泄漏的风险)。建议改为通过 `_get_osfhandle` / `_open_osfhandle` 获取句柄/文件描述符,并使用受支持的 `ofstream` 构造函数;或者干脆用 `std::ofstream(perf_path, ...)` 打开文件,然后再根据需要调整底层原生句柄(例如使用 `SetHandleInformation`),以保持行为在语义上清晰且具备良好可移植性。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • Consider explicitly deleting the move constructor (or defining a custom one) for PerfLogStream, since the current = default move with reference members can easily lead to multiple live objects referring to the same stream/mutex and cause confusing double-writes or use-after-move patterns.
  • The Windows-specific branch in perf_try_open_locked constructs std::ofstream from a FILE*, which is not a standard C++ constructor; it would be safer and more portable to either use _open_osfhandle/_fdopen with a file descriptor and then pass a path to std::ofstream, or otherwise rework this to rely on the standard ofstream API while still clearing handle inheritance.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider explicitly deleting the move constructor (or defining a custom one) for `PerfLogStream`, since the current `= default` move with reference members can easily lead to multiple live objects referring to the same stream/mutex and cause confusing double-writes or use-after-move patterns.
- The Windows-specific branch in `perf_try_open_locked` constructs `std::ofstream` from a `FILE*`, which is not a standard C++ constructor; it would be safer and more portable to either use `_open_osfhandle`/`_fdopen` with a file descriptor and then pass a path to `std::ofstream`, or otherwise rework this to rely on the standard `ofstream` API while still clearing handle inheritance.

## Individual Comments

### Comment 1
<location path="include/MaaUtils/LoggerUtils.h" line_range="182-186" />
<code_context>
+    PerfLogStream(const PerfLogStream&) = delete;
+    PerfLogStream(PerfLogStream&&) noexcept = default;
+
+    ~PerfLogStream()
+    {
+        std::unique_lock lock(mutex_);
+        if (stream_.is_open()) {
+            stream_ << buffer_.str() << '\n';
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider marking the PerfLogStream destructor noexcept and guarding against stream write exceptions.

This destructor does a stream write during stack unwinding but isn’t marked `noexcept`. If the underlying stream throws (e.g., via `exceptions()`/`badbit`), this can cause `std::terminate`. For a logging/perf utility, it’s usually better to treat I/O failures as non-fatal: wrap the write in a `try/catch` that swallows errors and mark the destructor `noexcept` to avoid crashing on log failures.
</issue_to_address>

### Comment 2
<location path="source/Logger/Logger.cpp" line_range="338-345" />
<code_context>
+        if (perf_ofs_.is_open()) {
+            perf_ofs_.close();
+        }
+#ifdef _WIN32
+        std::string str_path = perf_path.string();
+        FILE* fp = fopen(str_path.c_str(), "w");
+        if (!fp) {
+            return;
+        }
+        SetHandleInformation((HANDLE)_get_osfhandle(_fileno(fp)), HANDLE_FLAG_INHERIT, 0);
+        perf_ofs_ = std::ofstream(fp);
+#else
+        perf_ofs_ = std::ofstream(perf_path, std::ios::out | std::ios::trunc);
</code_context>
<issue_to_address>
**issue (bug_risk):** Using std::ofstream directly from a FILE* is non-standard and may cause portability/ownership issues.

On Windows, `perf_ofs_ = std::ofstream(fp);` is problematic because the standard library does not define an `ofstream` constructor taking a `FILE*`. This is likely a non-portable extension and makes it unclear whether `fp` or `perf_ofs_` owns the handle (risking double-close or a leak). Consider instead deriving a handle/descriptor via `_get_osfhandle` / `_open_osfhandle` and using a supported `ofstream` constructor, or simply opening via `std::ofstream(perf_path, ...)` and then adjusting the native handle (e.g., via `SetHandleInformation`) as needed to keep behavior portable and well-defined.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread include/MaaUtils/LoggerUtils.h
Comment thread source/Logger/Logger.cpp
@weypro
Copy link
Copy Markdown
Author

weypro commented May 11, 2026

关于 copy/move 的问题,我已经在最新提交中处理了,PerfLogStream 现在已经显式禁止 copy 和 move。

对于其他建议,我认为不适用于这个PR。当前实现是有意和现有 logger 保持一致。

如果我们希望修改文件打开方式、异常处理策略,或整体 stream 管理方式,我认为应该作为一个单独的 logging refactor 来规划,并且统一覆盖现有 logger 和新的 perf logger。

因此,这个 PR 中我倾向于只保留已经完成的 copy/move 修复,其余实现继续与现有代码保持一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant