| |
| #include "resource.hpp" |
| |
| #include <fmt/printf.h> |
| |
| #include <chrono> |
| #include <filesystem> |
| #include <regex> |
| #include <string> |
| |
| namespace boot_time_monitor |
| { |
| |
| namespace resource |
| { |
| |
| namespace fs = std::filesystem; |
| namespace btm = boot_time_monitor; |
| |
| // Definition of the global variable |
| std::string BTMonitorDir = "/var/google/boot_time_monitor/"; |
| |
| constexpr uint32_t kMaxCheckpointCnt = 100; |
| constexpr uint32_t kMaxDurationCnt = 100; |
| |
| File::File(std::string_view nodeName) : mNodeName(nodeName) |
| { |
| mCpOfs = StartFileInstance(GetCpFullFileName()); |
| mDurOfs = StartFileInstance(GetDurFullFileName()); |
| |
| mCheckpointCache = LoadCheckpoints(false); |
| mDurationCache = LoadDurations(false); |
| mCheckpointCompletedCache = LoadCheckpoints(true); |
| mDurationCompletedCache = LoadDurations(true); |
| } |
| |
| void File::SetCheckpoint(std::string_view name, int64_t epochTime, |
| int64_t duration) |
| { |
| auto wallTime = epochTime == 0 ? GetWallTimeInMs() : epochTime; |
| auto upTime = GetUpTimeInMs(); |
| if (upTime == std::nullopt) |
| { |
| fmt::print("[{}] Can't get up time. Skip this checkpoint.\n", |
| __FUNCTION__); |
| return; |
| } |
| if (!IsValidName(name)) |
| { |
| fmt::print( |
| "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n", |
| __FUNCTION__, name); |
| return; |
| } |
| |
| if (mCheckpointCache.size() >= kMaxCheckpointCnt) |
| { |
| fmt::print("[{}] Drop incoming checkpoint due to hit the limit. " |
| "name={}, epochTime={}, duration={}\n", |
| __FUNCTION__, name, epochTime, duration); |
| return; |
| } |
| // `boot_manager` helps to calculate the transition time from one stage to |
| // next stage if the user can provide a self measured duration for the |
| // current stage. |
| // For example, if the interval between checkpoint A and B is 10 seconds and |
| // the user knows B stage only uses 8 seconds to boot up. Then that means 2 |
| // seconds transition time was cost from A stage to B stage. |
| // `duration == 0` means "I don't have self measured duration, so no need to |
| // calculate transition time for me". |
| if (duration != 0) |
| { |
| AppendCheckpointToFile(std::string(name) + kBeginStageSuffix, |
| wallTime - duration, upTime.value() - duration); |
| |
| if (mCheckpointCache.size() >= kMaxCheckpointCnt) |
| { |
| fmt::print( |
| "[{}] Incoming checkpoint has `duration` so `To_{}` has been " |
| "created and added. Since the size is full so the incoming " |
| "checkpoint has been dropped\n", |
| __FUNCTION__, name); |
| return; |
| } |
| } |
| |
| AppendCheckpointToFile(std::string(name), wallTime, upTime.value()); |
| } |
| |
| void File::SetDuration(std::string_view name, int64_t duration) |
| { |
| if (!IsValidName(name)) |
| { |
| fmt::print( |
| "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n", |
| __FUNCTION__, name); |
| return; |
| } |
| |
| if (mDurationCache.size() >= kMaxDurationCnt) |
| { |
| fmt::print("[{}] Drop incoming duration due to hit the limit. " |
| "name={}, duration={}\n", |
| __FUNCTION__, name, duration); |
| return; |
| } |
| |
| AppendDurationToFile(std::string(name), duration); |
| } |
| |
| std::vector<btm::resource::Checkpoint> File::GetCheckpointCache() |
| { |
| return IsRebooting() ? mCheckpointCache : mCheckpointCompletedCache; |
| } |
| |
| std::vector<btm::resource::Duration> File::GetDurationCache() |
| { |
| return IsRebooting() ? mDurationCache : mDurationCompletedCache; |
| } |
| |
| void File::MarkComplete() |
| { |
| SetCheckpoint(kRebootCompleteCheckpoint, 0, 0); |
| std::swap(mCheckpointCompletedCache, mCheckpointCache); |
| mCheckpointCache.clear(); |
| std::swap(mDurationCompletedCache, mDurationCache); |
| mDurationCache.clear(); |
| |
| CompleteCurrentFile(mCpOfs, GetCpFullFileName()); |
| CompleteCurrentFile(mDurOfs, GetDurFullFileName()); |
| } |
| bool File::IsRebooting() |
| { |
| return !(IsEmptyFile(mCpOfs) && IsEmptyFile(mDurOfs)); |
| } |
| |
| std::ofstream File::StartFileInstance(std::string_view filename) |
| { |
| std::lock_guard<std::mutex> guard(mFileMutex); |
| fs::path dir = fs::path(filename).remove_filename(); |
| if (!dir.empty() && !fs::exists(dir)) |
| { |
| fs::create_directories(dir); |
| } |
| std::ofstream ofs = std::ofstream(std::string(filename), |
| std::fstream::out | std::fstream::app); |
| if (!ofs.good()) |
| { |
| throw std::invalid_argument("ofstream is not available."); |
| } |
| return ofs; |
| } |
| |
| bool File::IsEmptyFile(std::ofstream& ofs) |
| { |
| std::lock_guard<std::mutex> guard(mFileMutex); |
| const auto result = ofs.tellp(); |
| if (result < 0) |
| { |
| throw std::runtime_error("File can't be `tellp`"); |
| } |
| |
| return result == 0; |
| } |
| |
| void File::CompleteCurrentFile(std::ofstream& ofs, const std::string& filename) |
| { |
| std::lock_guard<std::mutex> guard(mFileMutex); |
| 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."); |
| } |
| } |
| |
| void File::AppendCheckpointToFile(const std::string& name, int64_t wallTimeInMs, |
| int64_t monoTimeInMs) |
| { |
| std::lock_guard<std::mutex> guard(mFileMutex); |
| mCheckpointCache.emplace_back(name, wallTimeInMs, monoTimeInMs); |
| mCpOfs << name << "," << wallTimeInMs << "," << monoTimeInMs << "\n"; |
| mCpOfs.flush(); |
| } |
| |
| void File::AppendDurationToFile(const std::string& name, int64_t durationInMs) |
| { |
| std::lock_guard<std::mutex> guard(mFileMutex); |
| mDurationCache.emplace_back(name, durationInMs); |
| mDurOfs << name << "," << durationInMs << "\n"; |
| mDurOfs.flush(); |
| } |
| |
| std::vector<btm::resource::Checkpoint> File::LoadCheckpoints(bool loadCompleted) |
| { |
| std::vector<btm::resource::Checkpoint> result = |
| std::vector<btm::resource::Checkpoint>(); |
| std::string cpFilename = GetCpFullFileName() + |
| (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::vector<btm::resource::Duration> File::LoadDurations(bool loadComplete) |
| { |
| std::vector<btm::resource::Duration> result = |
| std::vector<btm::resource::Duration>(); |
| std::string durFilename = GetDurFullFileName() + |
| (loadComplete ? 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; |
| } |
| |
| std::optional<int64_t> File::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); |
| } |
| |
| int64_t File::GetWallTimeInMs() |
| { |
| return std::chrono::system_clock::now().time_since_epoch() / |
| std::chrono::milliseconds(1); |
| } |
| |
| bool File::IsValidName(std::string_view name) |
| { |
| return !std::regex_search(std::string(name).c_str(), |
| std::regex("[^0-9a-zA-Z_:]")); |
| } |
| |
| inline std::string File::GetCpFullFileName() |
| { |
| return std::string() + btm::resource::BTMonitorDir + mNodeName + "_" + |
| kCheckpointFile; |
| } |
| inline std::string File::GetDurFullFileName() |
| { |
| return std::string() + btm::resource::BTMonitorDir + mNodeName + "_" + |
| kDurationFile; |
| } |
| } // namespace resource |
| } // namespace boot_time_monitor |