| #include "utils.hpp" |
| |
| #include <fmt/printf.h> |
| |
| #include <boost/interprocess/file_mapping.hpp> |
| #include <boost/interprocess/mapped_region.hpp> |
| |
| #include <array> |
| #include <chrono> |
| #include <filesystem> |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <regex> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| namespace boot_time_monitor |
| { |
| |
| namespace fs = std::filesystem; |
| |
| std::optional<int64_t> Util::getUpTimeInMs() |
| { |
| constexpr int32_t kMillisecondPerSecond = 1000; |
| std::ifstream fin("/proc/uptime"); |
| if (!fin.is_open()) |
| { |
| fmt::print(stderr, "[{}]: Can not read \"/proc/uptime\"\n", |
| __FUNCTION__); |
| return std::nullopt; |
| } |
| double uptimeSec; |
| fin >> uptimeSec; |
| fin.close(); |
| |
| return static_cast<int64_t>(uptimeSec * kMillisecondPerSecond); |
| } |
| |
| inline int64_t Util::getWallTimeInMs() |
| { |
| return std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1); |
| } |
| |
| inline bool Util::isValidName(std::string_view name) |
| { |
| return !std::regex_search(name.data(), std::regex("[^0-9a-zA-Z_:]")); |
| } |
| |
| inline std::string Util::getCPPath(std::string_view nodeName, |
| bool wantCompleted) |
| { |
| return kBTMonitorDir + nodeName.data() + "_" + kCheckpointFile + |
| (wantCompleted ? kCompletedSuffix : ""); |
| } |
| |
| inline std::string Util::getDurPath(std::string_view nodeName, |
| bool wantCompleted) |
| { |
| return kBTMonitorDir + nodeName.data() + "_" + kDurationFile + |
| (wantCompleted ? kCompletedSuffix : ""); |
| } |
| |
| std::optional<uint32_t> Util::readMem4Bytes(uint32_t target) |
| { |
| // Typically the pageSize will be 4K/8K for 32 bit operating systems |
| uint32_t pageSize = boost::interprocess::mapped_region::get_page_size(); |
| const uint32_t kPageMask = pageSize - 1; |
| |
| uint32_t pageOffset = target & (~kPageMask); |
| uint32_t offsetInPage = target & kPageMask; |
| |
| // Map `/dev/mem` to a region. |
| std::unique_ptr<boost::interprocess::file_mapping> fileMapping; |
| std::unique_ptr<boost::interprocess::mapped_region> MappedRegion; |
| try |
| { |
| fileMapping = std::make_unique<boost::interprocess::file_mapping>( |
| "/dev/mem", boost::interprocess::read_only); |
| // No need to unmap in the end. |
| MappedRegion = std::make_unique<boost::interprocess::mapped_region>( |
| *fileMapping, boost::interprocess::read_only, pageOffset, |
| pageSize * 2); |
| |
| // MappedRegion->get_address() returns (void*) which needs to covert |
| // into (char*) to make `+ offsetInPage` work. |
| // Then converts it again into (uint32_t*) since we read 4 bytes. |
| return *(reinterpret_cast<uint32_t*>( |
| static_cast<char*>(MappedRegion->get_address()) + offsetInPage)); |
| } |
| catch (const std::exception& e) |
| { |
| fmt::print(stderr, "[{}]: Throw exception: %s\n", __FUNCTION__, |
| e.what()); |
| return std::nullopt; |
| } |
| |
| fmt::print(stderr, "[{}]: Shouldn't go to this line.\n", __FUNCTION__); |
| return std::nullopt; |
| } |
| |
| FileUtil::FileUtil(std::string_view filename) : filename(filename) |
| { |
| fs::path dir = fs::path(filename).remove_filename(); |
| if (!dir.empty() && !fs::exists(dir)) |
| { |
| fs::create_directories(dir); |
| } |
| ofs = std::make_unique<std::ofstream>( |
| filename.data(), std::fstream::out | std::fstream::app); |
| if (!ofs->good()) |
| { |
| throw std::invalid_argument("ofstream is not available."); |
| } |
| } |
| |
| void FileUtil::addCheckpoint(std::string_view name, int64_t wallTimeInMs, |
| int64_t monoTimeInMs) |
| { |
| (*ofs) << name << "," << wallTimeInMs << "," << monoTimeInMs << std::endl; |
| ofs->flush(); |
| } |
| |
| void FileUtil::addDuration(std::string_view name, int64_t durationInMs) |
| { |
| (*ofs) << name << "," << durationInMs << std::endl; |
| ofs->flush(); |
| } |
| |
| void FileUtil::completeCurrent() |
| { |
| ofs->close(); |
| fs::rename(filename, filename + kCompletedSuffix); |
| |
| // Reopen for next reboot |
| ofs->open(filename.c_str(), std::fstream::out | std::fstream::app); |
| if (!ofs->good()) |
| { |
| throw std::invalid_argument("ofstream is not available."); |
| } |
| } |
| |
| std::unique_ptr<std::vector<Checkpoint>> |
| FileUtil::loadCheckpoints(bool loadCompleted) |
| { |
| std::unique_ptr<std::vector<Checkpoint>> result = |
| std::make_unique<std::vector<Checkpoint>>(); |
| std::string cpFilename = filename + (loadCompleted ? kCompletedSuffix : ""); |
| std::ifstream ifs(cpFilename); |
| if (!ifs.good()) |
| { |
| fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__, |
| cpFilename); |
| return result; |
| } |
| |
| constexpr uint32_t kBufSize = 1024; |
| constexpr uint32_t kMatchCount = 4; |
| |
| std::array<char, kBufSize> buf; |
| std::cmatch match; |
| const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+),([0-9]+)$"); |
| while (ifs.getline(buf.data(), kBufSize)) |
| { |
| if (regex_match(buf.data(), match, reg)) |
| { |
| if (match.size() != kMatchCount) |
| { |
| // This line is problematic. Skip it. |
| continue; |
| } |
| |
| result->emplace_back(match[1].str(), std::stoll(match[2].str()), |
| std::stoll(match[3].str())); |
| } |
| } |
| return result; |
| } |
| |
| std::unique_ptr<std::vector<Duration>> |
| FileUtil::loadDurations(bool loadCompleted) |
| { |
| std::unique_ptr<std::vector<Duration>> result = |
| std::make_unique<std::vector<Duration>>(); |
| std::string durFilename = filename + |
| (loadCompleted ? kCompletedSuffix : ""); |
| std::ifstream ifs(durFilename); |
| if (!ifs.good()) |
| { |
| fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__, |
| durFilename); |
| return result; |
| } |
| |
| constexpr uint32_t kBufSize = 1024; |
| constexpr uint32_t kMatchCount = 3; |
| |
| std::array<char, kBufSize> buf; |
| std::cmatch match; |
| const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+)$"); |
| while (ifs.getline(buf.data(), kBufSize)) |
| { |
| if (regex_match(buf.data(), match, reg)) |
| { |
| if (match.size() != kMatchCount) |
| { |
| // This line is problematic. Skip it. |
| continue; |
| } |
| |
| result->emplace_back(match[1].str(), std::stoll(match[2].str())); |
| } |
| } |
| return result; |
| } |
| |
| bool FileUtil::isEmpty() |
| { |
| const auto result = ofs->tellp(); |
| if (result < 0) |
| { |
| throw std::runtime_error("File can't be `tellp`"); |
| } |
| |
| return result == 0; |
| } |
| |
| } // namespace boot_time_monitor |