Skip to content

Commit eeafb63

Browse files
committed
feat: add perf trace log channel
1 parent 72c94c9 commit eeafb63

3 files changed

Lines changed: 161 additions & 10 deletions

File tree

include/MaaUtils/Logger.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ class MAA_UTILS_API Logger
1111
static constexpr std::string_view kLogFilename = "maafw.log";
1212
static constexpr std::string_view kLogbakFilename = "maafw.bak.{}.log";
1313

14+
// perf trace 通道(独立文件,独立锁,无 Logger 默认前缀,纯 CSV 行)
15+
static constexpr std::string_view kPerfTraceSubdir = "perf";
16+
static constexpr std::string_view kPerfTraceFilenameFormat = "maafw_perf_trace_{}.csv";
17+
static constexpr std::string_view kPerfTraceCsvHeader = "ts_us,tid,scope,name,extra,elapsed_us";
18+
1419
public:
1520
static Logger& get_instance();
1621

@@ -57,6 +62,11 @@ class MAA_UTILS_API Logger
5762
return stream(level::trace, std::forward<args_t>(args)...);
5863
}
5964

65+
// perf trace 通道入口。返回的 PerfLogStream 不带任何 Logger 默认前缀,
66+
// 调用方负责把整行 CSV 文本拼好。析构时一次性原子追加一行到 perf 文件。
67+
// perf 文件随 start_logging() 打开;若日志目录未设置,当前写入会静默丢弃。
68+
PerfLogStream perf_stream();
69+
6070
void start_logging(std::filesystem::path dir);
6171
void set_stdout_level(level lv);
6272
void flush();
@@ -82,6 +92,11 @@ class MAA_UTILS_API Logger
8292
void log_proc_info();
8393
void count_and_check_flush();
8494

95+
// perf 通道相关(实现见 Logger.cpp)
96+
void perf_reset_locked();
97+
void perf_try_open_locked();
98+
void perf_close_locked();
99+
85100
LogStream internal_dbg();
86101

87102
private:
@@ -97,6 +112,12 @@ class MAA_UTILS_API Logger
97112
std::mutex trace_mutex_;
98113

99114
size_t log_count_ = 0;
115+
116+
// perf 通道:与主日志完全分离
117+
std::mutex perf_init_mutex_; // 保护 perf 初始化状态及 log_dir_ 切换
118+
std::mutex perf_mutex_; // 保护对 perf_ofs_ 的实际写入
119+
std::ofstream perf_ofs_;
120+
bool perf_initialized_ = false;
100121
};
101122

102123
class LogScopeEnterHelper
@@ -164,6 +185,10 @@ MAA_LOG_NS_END
164185
#define LogDebug MAA_LOG_NS::Logger::get_instance().debug(LOG_ARGS)
165186
#define LogTrace MAA_LOG_NS::Logger::get_instance().trace(LOG_ARGS)
166187

188+
// perf 通道入口。无任何前缀,调用方拼好整行 CSV 文本即可。
189+
// 此宏在所有构建中都存在,供 MaaFramework 的 perf trace 功能按需调用。
190+
#define LogPerf MAA_LOG_NS::Logger::get_instance().perf_stream()
191+
167192
#define LogFunc \
168193
MAA_LOG_NS::LogScopeLeaveHelper ScopeHelperVarName(LOG_ARGS); \
169194
MAA_LOG_NS::LogScopeEnterHelper(LOG_ARGS)()

include/MaaUtils/LoggerUtils.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,48 @@ class MAA_UTILS_API LogStream
157157
std::stringstream buffer_;
158158
};
159159

160+
// perf trace 通道专用流。
161+
//
162+
// 跟 LogStream 的关键差别:
163+
// 1. 不写任何默认前缀 (无 timestamp/level/pid/tid 头部),调用方完全控制行内容
164+
// 2. 不写 stdout
165+
// 3. 不做 json 特殊化处理,纯粹 ostream 风格拼接
166+
// 4. 使用独立的 perf 文件 + 独立的 mutex,跟主日志互不干扰
167+
//
168+
// 析构时把 buffer 一次性原子追加一行到 perf 文件 (含尾部换行)。
169+
// 若 perf 文件未打开 (例如 log_dir 尚未配置),析构静默丢弃,不抛错。
170+
class MAA_UTILS_API PerfLogStream
171+
{
172+
public:
173+
PerfLogStream(std::mutex& m, std::ofstream& s)
174+
: mutex_(m)
175+
, stream_(s)
176+
{
177+
}
178+
179+
PerfLogStream(const PerfLogStream&) = delete;
180+
PerfLogStream(PerfLogStream&&) = delete;
181+
182+
~PerfLogStream()
183+
{
184+
std::unique_lock lock(mutex_);
185+
if (stream_.is_open()) {
186+
stream_ << buffer_.str() << '\n';
187+
}
188+
}
189+
190+
template <typename T>
191+
requires has_output_operator<T>
192+
PerfLogStream& operator<<(T&& value)
193+
{
194+
buffer_ << std::forward<T>(value);
195+
return *this;
196+
}
197+
198+
private:
199+
std::mutex& mutex_;
200+
std::ofstream& stream_;
201+
std::ostringstream buffer_;
202+
};
203+
160204
MAA_LOG_NS_END

source/Logger/Logger.cpp

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,21 @@ Logger& Logger::get_instance()
8484

8585
void Logger::start_logging(std::filesystem::path dir)
8686
{
87-
log_dir_ = std::move(dir);
88-
if (log_dir_.empty()) {
89-
log_path_.clear();
90-
}
91-
else {
92-
log_path_ = log_dir_ / kLogFilename;
87+
{
88+
std::unique_lock init_lock(perf_init_mutex_);
89+
90+
log_dir_ = std::move(dir);
91+
if (log_dir_.empty()) {
92+
log_path_.clear();
93+
}
94+
else {
95+
log_path_ = log_dir_ / kLogFilename;
96+
}
97+
98+
perf_reset_locked();
99+
perf_try_open_locked();
93100
}
101+
94102
reinit();
95103
}
96104

@@ -132,7 +140,7 @@ static void remove_old_files(const std::filesystem::path& dir)
132140
}
133141

134142
const auto ext = path_to_utf8_string(entry.path().extension());
135-
if (ext != ".log" && ext != ".jpg" && ext != ".png") {
143+
if (ext != ".log" && ext != ".jpg" && ext != ".png" && ext != ".csv") {
136144
continue;
137145
}
138146

@@ -215,9 +223,15 @@ void Logger::close()
215223
internal_dbg() << "Close log";
216224
internal_dbg() << kSplitLine;
217225

218-
std::unique_lock trace_lock(trace_mutex_);
219-
if (ofs_.is_open()) {
220-
ofs_.close();
226+
{
227+
std::unique_lock trace_lock(trace_mutex_);
228+
if (ofs_.is_open()) {
229+
ofs_.close();
230+
}
231+
}
232+
{
233+
std::unique_lock init_lock(perf_init_mutex_);
234+
perf_close_locked();
221235
}
222236
}
223237

@@ -282,4 +296,72 @@ LogStream Logger::internal_dbg()
282296
return debug("Logger");
283297
}
284298

299+
PerfLogStream Logger::perf_stream()
300+
{
301+
std::unique_lock init_lock(perf_init_mutex_);
302+
// 注意: 此处返回的 PerfLogStream 析构时会拿 perf_mutex_ 写。
303+
// 若 perf_ofs_ 没打开(log_dir 空,或打开失败),PerfLogStream 析构静默丢弃。
304+
return PerfLogStream(perf_mutex_, perf_ofs_);
305+
}
306+
307+
void Logger::perf_reset_locked()
308+
{
309+
// 切换 log_dir 时重置 perf 通道,随后由 start_logging() 按新目录重开
310+
perf_close_locked();
311+
perf_initialized_ = false;
312+
}
313+
314+
void Logger::perf_try_open_locked()
315+
{
316+
if (perf_initialized_) {
317+
return;
318+
}
319+
if (log_dir_.empty()) {
320+
return;
321+
}
322+
323+
std::error_code ec;
324+
const std::filesystem::path perf_dir = log_dir_ / kPerfTraceSubdir;
325+
std::filesystem::create_directories(perf_dir, ec);
326+
if (ec) {
327+
return;
328+
}
329+
330+
const auto filename = std::format(kPerfTraceFilenameFormat, format_now_for_filename());
331+
const std::filesystem::path perf_path = perf_dir / filename;
332+
333+
{
334+
std::unique_lock perf_lock(perf_mutex_);
335+
if (perf_ofs_.is_open()) {
336+
perf_ofs_.close();
337+
}
338+
#ifdef _WIN32
339+
std::string str_path = perf_path.string();
340+
FILE* fp = fopen(str_path.c_str(), "w");
341+
if (!fp) {
342+
return;
343+
}
344+
SetHandleInformation((HANDLE)_get_osfhandle(_fileno(fp)), HANDLE_FLAG_INHERIT, 0);
345+
perf_ofs_ = std::ofstream(fp);
346+
#else
347+
perf_ofs_ = std::ofstream(perf_path, std::ios::out | std::ios::trunc);
348+
#endif
349+
if (!perf_ofs_.is_open()) {
350+
return;
351+
}
352+
perf_ofs_ << kPerfTraceCsvHeader << '\n';
353+
}
354+
355+
perf_initialized_ = true;
356+
}
357+
358+
void Logger::perf_close_locked()
359+
{
360+
std::unique_lock perf_lock(perf_mutex_);
361+
if (perf_ofs_.is_open()) {
362+
perf_ofs_.flush();
363+
perf_ofs_.close();
364+
}
365+
}
366+
285367
MAA_LOG_NS_END

0 commit comments

Comments
 (0)