diff --git a/module/common/include/oops/str.h b/module/common/include/oops/str.h index 5265493..60606b9 100644 --- a/module/common/include/oops/str.h +++ b/module/common/include/oops/str.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -217,7 +218,17 @@ std::string ToLower(std::string_view s) noexcept; std::string ToUpper(std::string_view s) noexcept; std::string Repeat(const std::string &str, std::size_t n); -std::string Elide(std::string_view s, std::size_t n); + +template +std::string RemoveIf(std::string_view s, Pred &&pred) { + std::string res{s}; + res.erase(std::remove_if(res.begin(), res.end(), std::forward(pred)), res.end()); + return res; +} + +inline std::string Remove(std::string_view s, std::string_view chars = SPACE) { + return RemoveIf(s, [&chars](char c) { return chars.find(c) != std::string_view::npos; }); +} // 后续使用lexcial_cast替代 template diff --git a/module/common/src/str.cpp b/module/common/src/str.cpp index 8870e8b..44d4cf6 100644 --- a/module/common/src/str.cpp +++ b/module/common/src/str.cpp @@ -70,14 +70,4 @@ std::string Repeat(const std::string &str, std::size_t n) { } return res; } - -std::string Elide(std::string_view s, std::size_t n) { - if (s.size() <= n) { - return std::string{s}; - } - if (n <= 3) { - return std::string(n, '.'); - } - return std::string{s.substr(0, n - 3)} + "..."; -} } // namespace oops diff --git a/module/matrix/CMakeLists.txt b/module/matrix/CMakeLists.txt index ac4c680..1ed4761 100644 --- a/module/matrix/CMakeLists.txt +++ b/module/matrix/CMakeLists.txt @@ -28,8 +28,8 @@ if(ENABLE_TEST) target_link_libraries(oops_matrix_test_o PRIVATE oops_matrix_i gtest gmock) # 用例路径定义 - set(OOPS_CASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test/case") - target_compile_definitions(oops_matrix_test_o PRIVATE "OOPS_CASE_DIR=\"${OOPS_CASE_DIR}\"") + set(OOPS_MATRIX_CASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test/case") + target_compile_definitions(oops_matrix_test_o PRIVATE "OOPS_MATRIX_CASE_DIR=\"${OOPS_MATRIX_CASE_DIR}\"") # 构建模块测试程序 add_executable(oops_matrix_test) diff --git a/module/matrix/test/test_matrix_market_io.cpp b/module/matrix/test/test_matrix_market_io.cpp index 46bf221..6303efb 100644 --- a/module/matrix/test/test_matrix_market_io.cpp +++ b/module/matrix/test/test_matrix_market_io.cpp @@ -7,7 +7,7 @@ using namespace oops; namespace fs = std::filesystem; -inline const fs::path CASE_DIR{OOPS_CASE_DIR}; +inline const fs::path CASE_DIR{OOPS_MATRIX_CASE_DIR}; TEST(Coo, ReadCoo) { std::ifstream iss(CASE_DIR / "m_coo_real_sym.mtx"); diff --git a/module/profiling/CMakeLists.txt b/module/profiling/CMakeLists.txt index 31ad77d..ff2dd60 100644 --- a/module/profiling/CMakeLists.txt +++ b/module/profiling/CMakeLists.txt @@ -33,6 +33,10 @@ if(ENABLE_TEST) add_library(oops_profiling_test_o OBJECT ${TEST_SRC}) target_link_libraries(oops_profiling_test_o PRIVATE oops_profiling_i gtest) + # 用例路径定义 + set(OOPS_PROFILING_CASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test/case") + target_compile_definitions(oops_profiling_test_o PRIVATE "OOPS_PROFILING_CASE_DIR=\"${OOPS_PROFILING_CASE_DIR}\"") + # 构建模块测试程序 add_executable(oops_profiling_test) set_target_properties(oops_profiling_test PROPERTIES OUTPUT_NAME test_profiling) diff --git a/module/profiling/include/oops/system_info.h b/module/profiling/include/oops/system_info.h index 33a9a93..a165d4b 100644 --- a/module/profiling/include/oops/system_info.h +++ b/module/profiling/include/oops/system_info.h @@ -323,6 +323,7 @@ struct Info { [[nodiscard]] Info Get(); [[nodiscard]] Info Get(const FieldMask &field_mask); +[[nodiscard]] Info Get(std::istream &is); [[nodiscard]] Info Get(std::istream &is, const FieldMask &field_mask); std::ostream &operator<<(std::ostream &os, const Info &info); } // namespace cpuinfo diff --git a/module/profiling/src/system_info.cpp b/module/profiling/src/system_info.cpp index 8ca88e7..a6d7aa5 100644 --- a/module/profiling/src/system_info.cpp +++ b/module/profiling/src/system_info.cpp @@ -42,7 +42,7 @@ std::string GetCmdOutput(const std::string &cmd) { // 解析和格式化字段的特化注册 template -bool ParseField(std::string_view s, T &t, std::string_view ctx = "{}") { +bool ParseField(std::string_view s, T &t, std::string_view ctx) { auto scan_res{scn::scan(s, ctx)}; if (scan_res) { t = scan_res->value(); @@ -51,28 +51,23 @@ bool ParseField(std::string_view s, T &t, std::string_view ctx = "{}") { } template -std::string FormatField(const T &t, std::string_view ctx = "{}") { +std::string FormatField(const T &t, std::string_view ctx) { return fmt::format(ctx, t); } -// std::string特化:默认ctx升级为整行scanset,避免空格截断(如model name) template <> -bool ParseField(std::string_view s, std::string &t, std::string_view ctx) { - auto scan_res{scn::scan(s, ctx == "{}" ? std::string_view{"{:[^\n]}"} : ctx)}; - if (scan_res) { - t = std::move(scan_res->value()); - } - return bool{scan_res}; +bool ParseField(std::string_view s, std::string &t, std::string_view) { + t = s; + return true; } -// std::vector特化:空格分割的标志列表(如cpuinfo的flags) template <> bool ParseField(std::string_view s, std::vector &v, std::string_view) { v.clear(); for (auto token : Split(s)) { v.emplace_back(token); } - return !v.empty(); + return true; } template <> @@ -89,29 +84,49 @@ std::string FormatField(const std::vector &v, std::string_view) { return s; } -// bool特化:scn支持0/1/true/false,补充yes/no形式(如cpuinfo的fpu字段) +// bool特化,支持在ctx中定义代表true和false的字符串,例如"true/false"或"yes/no" template <> bool ParseField(std::string_view s, bool &b, std::string_view ctx) { - auto scan_res{scn::scan(s, ctx)}; - if (scan_res) { - b = scan_res->value(); - return true; + s = Strip(s); + + // 默认值 + std::string_view t{"1"}; + std::string_view f{"0"}; + + auto pos{ctx.find('/')}; + if (pos != std::string_view::npos) { + t = Strip(ctx.substr(0, pos)); + f = Strip(ctx.substr(pos + 1)); } - std::string_view v{Strip(s)}; - if (v == "yes") { + + if (s == t) { b = true; return true; } - if (v == "no") { + if (s == f) { b = false; return true; } return false; } +template <> +std::string FormatField(const bool &b, std::string_view ctx) { + // 默认值 + std::string_view t{"1"}; + std::string_view f{"0"}; + + auto pos{ctx.find('/')}; + if (pos != std::string_view::npos) { + t = Strip(ctx.substr(0, pos)); + f = Strip(ctx.substr(pos + 1)); + } + return std::string{b ? t : f}; +} + // Storage特化 template -bool ParseField(std::string_view s, Storage &storage, std::string_view ctx = "{}") { +bool ParseField(std::string_view s, Storage &storage, std::string_view ctx) { R r{}; auto res{ParseField(s, r, ctx)}; storage = Storage{r}; @@ -119,7 +134,7 @@ bool ParseField(std::string_view s, Storage &storage, std::string_view ctx } template -std::string FormatField(const Storage &storage, std::string_view ctx = "{}") { +std::string FormatField(const Storage &storage, std::string_view ctx) { return FormatField(storage.Count(), ctx); } @@ -254,12 +269,11 @@ class KeyValueParser { } std::string_view value{Strip(line.substr(pos + 1))}; - if (value.empty()) { - continue; + if (ParseMemberPtrVar(value, it->member_ptr_var, object, it->parse_ctx)) { + parsed |= it->field; + } else { + std::cerr << "Error: parse field '" << it->key << "' with value '" << value << "' failed" << std::endl; } - - ParseMemberPtrVar(value, it->member_ptr_var, object, it->parse_ctx); - parsed |= it->field; if (parsed == field_mask) { break; // 已解析全量 } @@ -276,18 +290,15 @@ class KeyValueParser { return parsed; } - // key-value渲染不是表格,不做列对齐,统一规则:key + delim + 空格 + value(+ 空格 + suffix) void Format(std::ostream &os, const Struct &object, const EnumBitset &field_mask = ~EnumBitset{}) { for (const auto &entry : field_table_) { if (field_mask.Test(entry.field)) { std::string value{FormatMemberPtrVar(entry.member_ptr_var, object, entry.format_ctx)}; - if (!value.empty()) { - os << entry.key << delim_ << ' ' << value; - if (!entry.suffix.empty()) { - os << ' ' << entry.suffix; - } - os << '\n'; + os << entry.key << delim_ << ' ' << value; + if (!entry.suffix.empty()) { + os << ' ' << entry.suffix; } + os << '\n'; } } } @@ -585,10 +596,10 @@ KeyValueParser< {Field::CPU_CORES, "cpu cores", &Entry::cpu_cores}, {Field::APICID, "apicid", &Entry::apicid}, {Field::INITIAL_APICID, "initial apicid", &Entry::initial_apicid}, - {Field::FPU, "fpu", &Entry::fpu}, - {Field::FPU_EXCEPTION, "fpu_exception", &Entry::fpu_exception}, + {Field::FPU, "fpu", &Entry::fpu, "", "yes/no", "yes/no"}, + {Field::FPU_EXCEPTION, "fpu_exception", &Entry::fpu_exception, "", "yes/no", "yes/no"}, {Field::CPUID_LEVEL, "cpuid level", &Entry::cpuid_level}, - {Field::WP, "wp", &Entry::wp}, + {Field::WP, "wp", &Entry::wp, "", "yes/no", "yes/no"}, {Field::FLAGS, "flags", &Entry::flags}, {Field::BUGS, "bugs", &Entry::bugs}, {Field::BOGOMIPS, "bogomips", &Entry::bogomips}, @@ -612,6 +623,8 @@ Info Get(std::istream &is, const FieldMask &field_mask) { Info Get() { return Get(~FieldMask{}); } +Info Get(std::istream &is) { return Get(is, ~FieldMask{}); } + Info Get(const FieldMask &field_mask) { std::ifstream ifs("/proc/cpuinfo"); return Get(ifs, field_mask); diff --git a/module/profiling/test/case/iZbp159dyz8itphnzxoylkZ/cpuinfo.txt b/module/profiling/test/case/iZbp159dyz8itphnzxoylkZ/cpuinfo.txt new file mode 100644 index 0000000..5afa593 --- /dev/null +++ b/module/profiling/test/case/iZbp159dyz8itphnzxoylkZ/cpuinfo.txt @@ -0,0 +1,54 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 85 +model name : Intel(R) Xeon(R) Platinum +stepping : 4 +microcode : 0x1 +cpu MHz : 2499.998 +cache size : 33792 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 1 +apicid : 0 +initial apicid : 0 +fpu : yes +fpu_exception : yes +cpuid level : 22 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves arat +bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data retbleed gds bhi its +bogomips : 4999.99 +clflush size : 64 +cache_alignment : 64 +address sizes : 46 bits physical, 48 bits virtual +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 85 +model name : Intel(R) Xeon(R) Platinum +stepping : 4 +microcode : 0x1 +cpu MHz : 2499.998 +cache size : 33792 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 1 +apicid : 1 +initial apicid : 1 +fpu : yes +fpu_exception : yes +cpuid level : 22 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves arat +bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data retbleed gds bhi its +bogomips : 4999.99 +clflush size : 64 +cache_alignment : 64 +address sizes : 46 bits physical, 48 bits virtual +power management: + diff --git a/module/profiling/test/test_system_info.cpp b/module/profiling/test/test_system_info.cpp index 3f5f3b6..836f769 100644 --- a/module/profiling/test/test_system_info.cpp +++ b/module/profiling/test/test_system_info.cpp @@ -1,11 +1,29 @@ #include +#include +#include +#include #include +#include #include "gtest/gtest.h" #include "oops/cpu_timer.h" +#include "oops/str.h" #include "oops/system_info.h" +namespace fs = std::filesystem; +static const fs::path CASE_DIR{OOPS_PROFILING_CASE_DIR}; + +static std::vector SplitAndSqueezeLines(std::string_view s) { + std::vector res; + std::istringstream iss{std::string{s}}; + std::string buf; + while (std::getline(iss, buf)) { + res.push_back(oops::RemoveIf(buf, oops::IsSpace)); + } + return res; +} + TEST(ProfilingSystemInfo, Status) { using namespace oops::proc; auto t1 = std::chrono::steady_clock::now(); @@ -55,19 +73,82 @@ TEST(ProfilingSystemInfo, SmapsRollup) { std::cout << proc::smaps_rollup::Get(); } -TEST(ProfilingSystemInfo, Cpuinfo) { - using namespace oops; - std::cout << proc::cpuinfo::Get(); +TEST(ProfilingSystemInfo, CpuinfoParse) { + namespace cpuinfo = oops::proc::cpuinfo; + + const fs::path CPUINFO_CASE{CASE_DIR / "iZbp159dyz8itphnzxoylkZ" / "cpuinfo.txt"}; + std::cout << CPUINFO_CASE << std::endl; + std::ifstream ifs{CPUINFO_CASE}; + ASSERT_TRUE(ifs.is_open()); + + auto info{cpuinfo::Get(ifs)}; + ASSERT_EQ(info.table.size(), 2); + + for (std::size_t i{0}; i < info.table.size(); ++i) { + const auto &entry{info.table[i]}; + EXPECT_EQ(entry.parsed, ~cpuinfo::FieldMask{}); + + // 标识 + EXPECT_EQ(entry.processor, i); + EXPECT_EQ(entry.vendor_id, "GenuineIntel"); + EXPECT_EQ(entry.cpu_family, 6); + EXPECT_EQ(entry.model, 85); + EXPECT_EQ(entry.model_name, "Intel(R) Xeon(R) Platinum"); + EXPECT_EQ(entry.stepping, 4); + EXPECT_EQ(entry.microcode, 1); + + // 规格 + EXPECT_DOUBLE_EQ(entry.cpu_mhz, 2499.998); + EXPECT_EQ(entry.cache_size.Count(), 33792); + + // 拓扑 + EXPECT_EQ(entry.physical_id, 0); + EXPECT_EQ(entry.siblings, 2); + EXPECT_EQ(entry.core_id, 0); + EXPECT_EQ(entry.cpu_cores, 1); + EXPECT_EQ(entry.apicid, i); + EXPECT_EQ(entry.initial_apicid, i); + + // 特性 + EXPECT_TRUE(entry.fpu); + EXPECT_TRUE(entry.fpu_exception); + EXPECT_EQ(entry.cpuid_level, 22); + EXPECT_TRUE(entry.wp); + EXPECT_EQ(entry.flags.size(), 81); + EXPECT_EQ(entry.flags.front(), "fpu"); + EXPECT_EQ(entry.flags.back(), "arat"); + EXPECT_EQ(entry.bugs.size(), 13); + EXPECT_EQ(entry.bugs.front(), "cpu_meltdown"); + EXPECT_EQ(entry.bugs.back(), "its"); + + // 其它 + EXPECT_DOUBLE_EQ(entry.bogomips, 4999.99); + EXPECT_EQ(entry.clflush_size, 64); + EXPECT_EQ(entry.cache_alignment, 64); + EXPECT_EQ(entry.address_sizes, "46 bits physical, 48 bits virtual"); + EXPECT_TRUE(entry.power_management.empty()); + } } -TEST(ProfilingSystemInfo, Cpuinfo2) { - using namespace oops; - // 主频监视典型用法:只提取processor和cpu MHz - auto info = proc::cpuinfo::Get(proc::cpuinfo::Field::PROCESSOR | proc::cpuinfo::Field::CPU_MHZ); - for (const auto &entry : info.table) { - ASSERT_TRUE(entry.parsed.Test(proc::cpuinfo::Field::PROCESSOR)); - ASSERT_TRUE(entry.parsed.Test(proc::cpuinfo::Field::CPU_MHZ)); - ASSERT_GT(entry.cpu_mhz, 0); +TEST(ProfilingSystemInfo, CpuinfoFormat) { + namespace cpuinfo = oops::proc::cpuinfo; + + const fs::path CPUINFO_CASE{CASE_DIR / "iZbp159dyz8itphnzxoylkZ" / "cpuinfo.txt"}; + std::ifstream ifs{CPUINFO_CASE}; + ASSERT_TRUE(ifs.is_open()); + std::stringstream buf; + buf << ifs.rdbuf(); + auto expected{SplitAndSqueezeLines(buf.str())}; + + ifs.clear(); // 恢复被rdbuf读取耗尽的流 + ifs.seekg(0); + auto info{cpuinfo::Get(ifs)}; + std::ostringstream oss; + oss << info; + auto actual{SplitAndSqueezeLines(oss.str())}; + + ASSERT_EQ(actual.size(), expected.size()); + for (std::size_t i{0}; i < actual.size(); ++i) { + EXPECT_EQ(actual[i], expected[i]); } - std::cout << info; }