blob: a78e4420b577ebb01196012c633aac1a775b2781 [file] [log] [blame]
#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