feat: add perf trace log channel#17
Open
weypro wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些整体性的反馈:
- 建议对
PerfLogStream显式删除移动构造函数(或自定义一个移动构造函数),因为当前带有引用成员并使用= default的移动构造很容易导致多个存活对象引用同一个流/互斥量,从而造成令人困惑的重复写入或 use-after-move 模式。 - 在
perf_try_open_locked的 Windows 特定分支中,使用FILE*来构造std::ofstream,这并不是标准 C++ 所定义的构造方式;为了更安全并具备更好的可移植性,建议要么结合_open_osfhandle/_fdopen和文件描述符,然后通过路径传给std::ofstream,要么以其他方式重构这段代码,使其依然能清除句柄继承,但对外只依赖标准的ofstreamAPI。
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>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
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= defaultmove 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_lockedconstructsstd::ofstreamfrom aFILE*, which is not a standard C++ constructor; it would be safer and more portable to either use_open_osfhandle/_fdopenwith a file descriptor and then pass a path tostd::ofstream, or otherwise rework this to rely on the standardofstreamAPI 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Author
|
关于 copy/move 的问题,我已经在最新提交中处理了, 对于其他建议,我认为不适用于这个PR。当前实现是有意和现有 logger 保持一致。 如果我们希望修改文件打开方式、异常处理策略,或整体 stream 管理方式,我认为应该作为一个单独的 logging refactor 来规划,并且统一覆盖现有 logger 和新的 perf logger。 因此,这个 PR 中我倾向于只保留已经完成的 copy/move 修复,其余实现继续与现有代码保持一致。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Enhancements: